From 334ebbcc9dd09578af471d126a27261a64893660 Mon Sep 17 00:00:00 2001 From: Yun-Jinwoo Date: Tue, 29 Apr 2025 22:32:55 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B2=AB=20=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EB=95=8C=20=EC=B4=88=EA=B8=B0=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EB=A7=8C=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Recipient/Recipient.jsx | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/pages/Recipient/Recipient.jsx b/src/pages/Recipient/Recipient.jsx index cfef512..984358a 100644 --- a/src/pages/Recipient/Recipient.jsx +++ b/src/pages/Recipient/Recipient.jsx @@ -20,6 +20,7 @@ export default function Recipient({ showDelete }) { const [hasNextMessage, setHasNextMessage] = useState(false); const [selectedCardId, setSelectedCardId] = useState(null); const observerRef = useRef(); + const hasInitialized = useRef(false); const navigate = useNavigate(); useEffect(() => { @@ -50,21 +51,21 @@ export default function Recipient({ showDelete }) { new Map(combined.map((message) => [message.id, message])).values(), ); - return uniqueMessages; - }); - - if (showDelete) { - setMessages(allMessages); - } else { - if ( - allMessages.length % 6 === 0 && - allMessages.length !== newMessages.count - ) { - setMessages(allMessages.slice(0, allMessages.length - 1)); + if (showDelete) { + setMessages(uniqueMessages); } else { - setMessages(allMessages); + if ( + uniqueMessages.length % 6 === 0 && + uniqueMessages.length !== newMessages.count + ) { + setMessages(uniqueMessages.slice(0, uniqueMessages.length - 1)); + } else { + setMessages(uniqueMessages); + } } - } + + return uniqueMessages; + }); if (!postData) return; setHasNextMessage(offset < postData.messageCount); setLoading(false); @@ -82,6 +83,10 @@ export default function Recipient({ showDelete }) { useEffect(() => { const observer = new IntersectionObserver((entries) => { const firstEntry = entries[0]; + if (!hasInitialized.current) { + hasInitialized.current = true; + return; // 첫 실행은 무시 + } if (firstEntry.isIntersecting && hasNextMessage && !loading) { loadMoreMessages(); } @@ -96,6 +101,7 @@ export default function Recipient({ showDelete }) { if (loading || !hasNextMessage) return; setLoading(true); const limit = 6; + console.log('load'); setOffset((prev) => prev + limit); }; From c18d9b8a061595faa6d5e6272d31aa19af40ef45 Mon Sep 17 00:00:00 2001 From: Yun-Jinwoo Date: Tue, 29 Apr 2025 22:35:22 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=92=A4=EB=A1=9C?= =?UTF-8?q?=EA=B0=80=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20(=ED=8E=B8=EC=A7=91?= =?UTF-8?q?=ED=95=98=EA=B8=B0=EC=97=90=EC=84=9C=EB=8A=94=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Recipient/Recipient.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Recipient/Recipient.jsx b/src/pages/Recipient/Recipient.jsx index 984358a..bbdc257 100644 --- a/src/pages/Recipient/Recipient.jsx +++ b/src/pages/Recipient/Recipient.jsx @@ -132,7 +132,7 @@ export default function Recipient({ showDelete }) { } function handleGoBack() { - navigate(-1); + showDelete ? navigate(`/post/${id}/`) : navigate('/list'); } function handleEditClick(id) { From 638c93c145a86f137adc5ea6acc8267f790e1d49 Mon Sep 17 00:00:00 2001 From: Yun-Jinwoo Date: Tue, 29 Apr 2025 22:57:34 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=B0=BD=EC=97=90=EC=84=9C=20=EB=B0=94=EA=B9=A5=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=8B=9C=20=EB=8B=AB=ED=9E=88=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Modal/Modal.jsx | 17 +++++++++++++---- src/hooks/useModalClose.jsx | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 src/hooks/useModalClose.jsx diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx index a4476eb..694e5d6 100644 --- a/src/components/Modal/Modal.jsx +++ b/src/components/Modal/Modal.jsx @@ -1,8 +1,9 @@ -import { useState } from 'react'; +import { useRef, useState, useEffect, useCallback } from 'react'; import DOMPurify from 'dompurify'; import ReactDOM from 'react-dom'; import Badge from '../Badge/Badge'; import Button from '../common/Button'; +import useModalClose from '../../hooks/useModalClose'; // 경로에 맞게 수정 import styles from './Modal.module.scss'; export default function Modal({ @@ -14,19 +15,27 @@ export default function Modal({ createdAt, onClose, }) { + const modalRef = useRef(null); const [isClosing, setIsClosing] = useState(false); const sanitizedHTML = DOMPurify.sanitize(children); - const handleCloseModal = () => { + // 닫기 로직 (애니메이션 포함) + const handleCloseModal = useCallback(() => { setIsClosing(true); setTimeout(() => { onClose(); }, 300); - }; + }, [onClose]); + + // 외부 클릭 감지하여 닫기 + useModalClose(modalRef, handleCloseModal); return ReactDOM.createPortal(
-
+
프로필 이미지 diff --git a/src/hooks/useModalClose.jsx b/src/hooks/useModalClose.jsx new file mode 100644 index 0000000..c086fac --- /dev/null +++ b/src/hooks/useModalClose.jsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; + +export default function useDetectClose(ref, onClose) { + useEffect(() => { + const handleClick = (e) => { + if (ref.current && !ref.current.contains(e.target)) { + onClose(); // 바로 닫기 말고 외부에서 애니메이션 포함한 onClose 실행 + } + }; + document.addEventListener('mousedown', handleClick); + return () => { + document.removeEventListener('mousedown', handleClick); + }; + }, [ref, onClose]); +} From 9e3e35f8c3eaeca7c5dcaee262d28f03101fc8e6 Mon Sep 17 00:00:00 2001 From: Yun-Jinwoo Date: Tue, 29 Apr 2025 22:58:11 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=90=9B=20fix=20:=20=EC=B2=AB=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EB=95=8C=20=EB=AC=B4=ED=95=9C=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=A0=81=EC=9A=A9=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Recipient/Recipient.jsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/Recipient/Recipient.jsx b/src/pages/Recipient/Recipient.jsx index bbdc257..afac477 100644 --- a/src/pages/Recipient/Recipient.jsx +++ b/src/pages/Recipient/Recipient.jsx @@ -20,7 +20,6 @@ export default function Recipient({ showDelete }) { const [hasNextMessage, setHasNextMessage] = useState(false); const [selectedCardId, setSelectedCardId] = useState(null); const observerRef = useRef(); - const hasInitialized = useRef(false); const navigate = useNavigate(); useEffect(() => { @@ -83,10 +82,7 @@ export default function Recipient({ showDelete }) { useEffect(() => { const observer = new IntersectionObserver((entries) => { const firstEntry = entries[0]; - if (!hasInitialized.current) { - hasInitialized.current = true; - return; // 첫 실행은 무시 - } + if (firstEntry.isIntersecting && hasNextMessage && !loading) { loadMoreMessages(); } @@ -101,7 +97,6 @@ export default function Recipient({ showDelete }) { if (loading || !hasNextMessage) return; setLoading(true); const limit = 6; - console.log('load'); setOffset((prev) => prev + limit); };