From f3128e7cdf2468f72db83ab25fdd363182c4f03e Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 13:54:32 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat=20[#78]=20Modal=20component=EA=B0=80?= =?UTF-8?q?=20content=EB=A5=BC=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=EC=B1=85=EC=9E=84=EB=A7=8C=20=EA=B0=96=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modal 방식을 범용적으로 사용할 수 있도록 '확인' 버튼을 제거합니다. - MUI의 Modal component interface를 참고하였습니다. - 이 변경사항이 영향을 미치는 곳을 아직 수정하지 않았으므로, 이 commit에서는 error가 발생합니다. --- src/components/modal/modal.jsx | 29 +++-------------------------- src/hooks/use-modal.jsx | 3 +-- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx index deb75b5..631f61a 100644 --- a/src/components/modal/modal.jsx +++ b/src/components/modal/modal.jsx @@ -1,7 +1,4 @@ import styled from "styled-components"; -import { useModal } from "../../hooks/use-modal"; -import { PrimaryButton } from "../button/button"; -import BUTTON_SIZE from "../button/button-size"; import Portal from "../portal/portal"; const Content = styled.div` @@ -34,34 +31,14 @@ const ModalContainer = styled.div` align-items: center; `; -const ActionButton = styled.div` - cursor: pointer; -`; - -function Modal({ id, action, children }) { - const { showsModal, setShowsModal } = useModal({ - id: id, - type: "modal", - }); - - const handleClick = () => setShowsModal(true); - const handleConfirmClick = () => setShowsModal(false); - +function Modal({ shows, children }) { return ( <> - {action} - {showsModal && ( + {shows && ( - - {children} - - + {children} diff --git a/src/hooks/use-modal.jsx b/src/hooks/use-modal.jsx index 9a3a1ad..964b050 100644 --- a/src/hooks/use-modal.jsx +++ b/src/hooks/use-modal.jsx @@ -1,7 +1,6 @@ import { usePortal } from "./use-portal"; -function useModal({ id, type }) { - const key = `${type}_${id}`; +function useModal({ key }) { const { isOpen, setIsOpen } = usePortal({ key }); return { showsModal: isOpen, setShowsModal: setIsOpen }; } From bdf71670a48bd6d1ba1de99aa40a9755d9593de7 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 13:55:06 +0900 Subject: [PATCH 2/8] =?UTF-8?q?refactor=20[#78]=20`TestComponentsPage`?= =?UTF-8?q?=EC=97=90=20`Modal`=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tests/test-components-page.jsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/tests/test-components-page.jsx b/src/tests/test-components-page.jsx index 4fd191f..2a5444f 100644 --- a/src/tests/test-components-page.jsx +++ b/src/tests/test-components-page.jsx @@ -23,6 +23,7 @@ import POPOVER_ALIGNMENT from "../components/popover/popover-alignment"; import TextField from "../components/text-field/text-field"; import TEXT_FIELD_TYPE from "../components/text-field/text-field-type"; import Toast from "../components/toast/toast"; +import { useModal } from "../hooks/use-modal"; import { useToast } from "../hooks/use-toast"; const OutlinedHeader = styled(Header)` @@ -49,6 +50,13 @@ function TestComponentsPage() { const handleToastClick = () => setShowsToast(true); const handleToastDismiss = () => setShowsToast(false); + /* Modal */ + const { showsModal, setShowsModal } = useModal({ + key: "test-modal", + }); + const handleModalOpen = () => setShowsModal(true); + const handleModalClose = () => setShowsModal(false); + return (
- } - > + +

This is Modal.

+
From 7e28955c511da2620268ba7dbb1c013555706f1f Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 13:55:29 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor=20[#78]=20`MessagesGrid`=EC=97=90?= =?UTF-8?q?=20`Modal`=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/message-card-detail.jsx | 23 +++++++- .../message/components/message-card.jsx | 17 +++++- .../message/components/messages-grid.jsx | 58 ++++++++++++------- 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/features/message/components/message-card-detail.jsx b/src/features/message/components/message-card-detail.jsx index 2f90ae9..68f8ad0 100644 --- a/src/features/message/components/message-card-detail.jsx +++ b/src/features/message/components/message-card-detail.jsx @@ -1,9 +1,12 @@ import styled from "styled-components"; +import { PrimaryButton } from "../../../components/button/button"; +import BUTTON_SIZE from "../../../components/button/button-size"; import Colors from "../../../components/color/colors"; import { formatDate } from "../../../utils/formatter"; import MessageSender from "./message-sender"; const Header = styled.header` + width: 100%; display: flex; justify-content: space-between; align-items: center; @@ -12,6 +15,7 @@ const Header = styled.header` `; const Content = styled.div` + width: 100%; margin-top: 16px; font-size: 18px; font-weight: 400; @@ -34,6 +38,10 @@ const Content = styled.div` } `; +const Action = styled.div` + padding-top: 24px; +`; + const CreatedDate = styled.span` font-size: 14px; font-weight: 400; @@ -41,9 +49,13 @@ const CreatedDate = styled.span` color: ${Colors.gray(400)}; `; -const StyledMessageCardDetail = styled.div``; +const StyledMessageCardDetail = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; -function MessageCardDetail({ message }) { +function MessageCardDetail({ message, onConfirm }) { return (
@@ -55,6 +67,13 @@ function MessageCardDetail({ message }) { {formatDate(message.createdAt, ".")}
{message.content} + + +
); } diff --git a/src/features/message/components/message-card.jsx b/src/features/message/components/message-card.jsx index d20a7cd..f1db29b 100644 --- a/src/features/message/components/message-card.jsx +++ b/src/features/message/components/message-card.jsx @@ -50,11 +50,22 @@ const StyledMessageCard = styled.article` border-radius: 16px; background-color: white; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08); + cursor: ${({ $isEditing }) => ($isEditing ? "default" : "pointer")}; `; -function MessageCard({ isEditing, message, onDelete }) { +function MessageCard({ isEditing, message, onClick, onDelete }) { + const handleClick = () => { + if (isEditing) return; + onClick(message); + }; + + const handleDeleteClick = (event) => { + event.stopPropagation(); + onDelete(message.id); + }; + return ( - +
)}
diff --git a/src/features/message/components/messages-grid.jsx b/src/features/message/components/messages-grid.jsx index 0e42b5e..47f7729 100644 --- a/src/features/message/components/messages-grid.jsx +++ b/src/features/message/components/messages-grid.jsx @@ -1,8 +1,9 @@ -import { useRef } from "react"; +import { useRef, useState } from "react"; import { useNavigate, useParams } from "react-router"; import styled from "styled-components"; import Modal from "../../../components/modal/modal.jsx"; import { useIntersectionObserver } from "../../../hooks/use-intersection-observer.jsx"; +import { useModal } from "../../../hooks/use-modal.jsx"; import { media } from "../../../utils/media.js"; import MessageCardAdd from "./message-card-add.jsx"; import MessageCardDetail from "./message-card-detail.jsx"; @@ -28,6 +29,10 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { const navigate = useNavigate(); const { id } = useParams(); const infiniteScrollTargetRef = useRef(); + const { showsModal, setShowsModal } = useModal({ + key: "message-modal", + }); + const [modalMessage, setModalMessage] = useState(null); const observerCallback = (entry) => { if (!entry.isIntersecting) return; @@ -39,33 +44,42 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { navigate(`/post/${id}/message`); }; + const handleMessageClick = (message) => { + setShowsModal(true); + setModalMessage(message); + }; + const handleDeleteClick = (messageId) => { onDelete(messageId); }; - const messageCard = (message) => ( - handleDeleteClick(message.id)} - /> - ); + const handleModalConfirm = () => { + setShowsModal(false); + setModalMessage(null); + }; return ( - - - {messages.map((message) => - isEditing ? ( - messageCard(message) - ) : ( - - - - ) - )} -
-
+ <> + + + {messages.map((message) => ( + + ))} +
+
+ + + + ); } From 34c1947086a88ea074a2fc3fcde6a83278409e68 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 21:17:33 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor=20[]=20'=EC=99=84=EB=A3=8C?= =?UTF-8?q?=ED=95=98=EA=B8=B0'=20=EB=B2=84=ED=8A=BC=EC=9D=98=20event=20han?= =?UTF-8?q?dler=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/messages-page.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx index 5584178..61d6ac8 100644 --- a/src/pages/messages-page.jsx +++ b/src/pages/messages-page.jsx @@ -75,7 +75,7 @@ function ViewerButtons({ onEdit }) { ); } -function EditingButtons({ onDelete, onCancel }) { +function EditingButtons({ onDelete, onDone }) { return ( ); @@ -119,7 +119,7 @@ function MessagesPage() { } }; - const handleEditCancel = () => { + const handleEditDone = () => { navigate(-1); }; @@ -186,7 +186,7 @@ function MessagesPage() { {isEditing ? ( ) : ( From 0442b5a6912aea48c70e155c740e31e547ea476a Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 22:52:29 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat=20[#84]=20`ModalDialog`=20component=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/modal/modal-dialog.jsx | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/components/modal/modal-dialog.jsx diff --git a/src/components/modal/modal-dialog.jsx b/src/components/modal/modal-dialog.jsx new file mode 100644 index 0000000..45a9ddc --- /dev/null +++ b/src/components/modal/modal-dialog.jsx @@ -0,0 +1,40 @@ +import styled from "styled-components"; +import { PrimaryButton } from "../button/button"; +import BUTTON_SIZE from "../button/button-size"; +import Colors from "../color/colors"; + +const Title = styled.h2` + margin: 0; + color: ${Colors.gray(600)}; +`; + +const Content = styled.p` + margin: 0; + color: ${Colors.gray(600)}; +`; + +const Action = styled.div` + display: flex; + justify-content: flex-end; + gap: 16px; +`; + +const StyledAlertDialog = styled.div` + display: flex; + flex-direction: column; + gap: 24px; +`; + +function ModalDialog({ title, content, action }) { + return ( + + {title} + {content && {content}} + + {action ?? } + + + ); +} + +export default ModalDialog; From b370abb2e557491733bb819e28f0b314850385fb Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 23:18:17 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat=20[#84]=20Modal=20dialog=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20cus?= =?UTF-8?q?tom=20hook=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/use-modal-dialog.jsx | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/hooks/use-modal-dialog.jsx diff --git a/src/hooks/use-modal-dialog.jsx b/src/hooks/use-modal-dialog.jsx new file mode 100644 index 0000000..5bde450 --- /dev/null +++ b/src/hooks/use-modal-dialog.jsx @@ -0,0 +1,41 @@ +import { useState } from "react"; +import { useModal } from "./use-modal"; + +function useModalDialog() { + const { showsModal, setShowsModal } = useModal({ + key: "delete-modal", + }); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [primaryAction, setPrimaryAction] = useState(null); + + const openDialog = ({ title, content, primaryAction }) => { + setShowsModal(true); + setTitle(title); + setContent(content); + setPrimaryAction(() => primaryAction); + }; + + const closeDialog = () => { + setShowsModal(false); + setTitle(""); + setContent(""); + setPrimaryAction(null); + }; + + const onPrimaryAction = () => { + primaryAction(); + closeDialog(); + }; + + return { + showsDialog: showsModal, + dialogTitle: title, + dialogContent: content, + openDialog, + closeDialog, + onPrimaryAction, + }; +} + +export { useModalDialog }; From 66a5e9ae610c49ffd36c325182190c8d004d7c76 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 23:18:39 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat=20[#84]=20=EB=A1=A4=EB=A7=81=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=ED=8D=BC=20=EB=B0=8F=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=EB=A5=BC=20=EC=82=AD=EC=A0=9C=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=84=EC=97=90=20dialog=EB=A1=9C=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/components/message-card.jsx | 2 +- .../message/components/messages-grid.jsx | 4 +- src/pages/messages-page.jsx | 100 ++++++++++++++---- 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/src/features/message/components/message-card.jsx b/src/features/message/components/message-card.jsx index f1db29b..1400b3c 100644 --- a/src/features/message/components/message-card.jsx +++ b/src/features/message/components/message-card.jsx @@ -61,7 +61,7 @@ function MessageCard({ isEditing, message, onClick, onDelete }) { const handleDeleteClick = (event) => { event.stopPropagation(); - onDelete(message.id); + onDelete(message); }; return ( diff --git a/src/features/message/components/messages-grid.jsx b/src/features/message/components/messages-grid.jsx index 47f7729..7aae610 100644 --- a/src/features/message/components/messages-grid.jsx +++ b/src/features/message/components/messages-grid.jsx @@ -49,8 +49,8 @@ function MessagesGrid({ isEditing, messages, onDelete, onInfiniteScroll }) { setModalMessage(message); }; - const handleDeleteClick = (messageId) => { - onDelete(messageId); + const handleDeleteClick = (message) => { + onDelete(message); }; const handleModalConfirm = () => { diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx index 61d6ac8..8574a9d 100644 --- a/src/pages/messages-page.jsx +++ b/src/pages/messages-page.jsx @@ -5,9 +5,12 @@ import { DangerousButton, OutlinedButton, PrimaryButton, + SecondaryButton, } from "../components/button/button"; import BUTTON_SIZE from "../components/button/button-size"; import BACKGROUND_COLOR from "../components/color/background-color"; +import Modal from "../components/modal/modal"; +import ModalDialog from "../components/modal/modal-dialog"; import { deleteMessage, getMessages, @@ -20,6 +23,7 @@ import { } from "../features/rolling-paper/api/recipients"; import RollingPaperHeader from "../features/rolling-paper/components/header/rolling-paper-header"; import { useMedia } from "../hooks/use-media"; +import { useModalDialog } from "../hooks/use-modal-dialog"; import ContentLayout from "../layouts/content-layout"; import { media } from "../utils/media"; @@ -83,11 +87,7 @@ function EditingButtons({ onDelete, onDone }) { title="삭제하기" onClick={onDelete} /> - + ); } @@ -99,6 +99,14 @@ function MessagesPage() { const location = useLocation(); const navigate = useNavigate(); const { id } = useParams(); + const { + showsDialog, + dialogTitle, + dialogContent, + openDialog, + closeDialog, + onPrimaryAction, + } = useModalDialog(); const isEditing = useMemo( () => location.pathname.includes("edit"), @@ -109,28 +117,50 @@ function MessagesPage() { navigate("edit"); }; - const handleRollingPaperDelete = async () => { - try { - await deleteRecipient({ id: recipient.id }); - navigate(`/list`); - } catch (error) { - // TODO: Error 처리 - console.log(error); - } + const handleRollingPaperDelete = () => { + openDialog({ + title: `${recipient.name} 님의 롤링 페이퍼를 삭제할까요?`, + content: "삭제한 롤링 페이퍼는 복원할 수 없어요.", + primaryAction: async () => { + try { + await deleteRecipient({ id: recipient.id }); + navigate(`/list`); + } catch (error) { + // TODO: Error 처리 + console.log(error); + } + }, + }); }; const handleEditDone = () => { navigate(-1); }; - const handleMessageDelete = async (messageId) => { - try { - await deleteMessage({ id: messageId }); - setMessages((prev) => prev.filter((message) => message.id !== messageId)); - } catch (error) { - // TODO: Error 처리 - console.log(error); - } + const handleMessageDelete = (message) => { + openDialog({ + title: `${message.sender} 님의 메시지를 삭제할까요?`, + content: "삭제한 메시지는 복원할 수 없어요.", + primaryAction: async () => { + try { + await deleteMessage({ id: message.id }); + setMessages((prev) => + prev.filter((prevMessage) => prevMessage.id !== message.id) + ); + } catch (error) { + // TODO: Error 처리 + console.log(error); + } + }, + }); + }; + + const handleDelete = () => { + onPrimaryAction(); + }; + + const handleDeleteCancel = () => { + closeDialog(); }; const handleInfiniteScroll = async () => { @@ -204,7 +234,33 @@ function MessagesPage() { ); - return isMobile ? content : {content}; + return isMobile ? ( + content + ) : ( + + {content} + + + + + + } + /> + + + ); } export default MessagesPage; From c15879f82388e9b7c7180a0e0d27636c3d976051 Mon Sep 17 00:00:00 2001 From: Chamsol Kim Date: Sat, 23 Aug 2025 23:22:24 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor=20[#84]=20=EC=8B=A4=EC=A0=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EB=8A=94=20=EB=B2=84=ED=8A=BC=EC=97=90?= =?UTF-8?q?=EB=A7=8C=20`DangerousButton`=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/messages-page.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx index 8574a9d..41030f0 100644 --- a/src/pages/messages-page.jsx +++ b/src/pages/messages-page.jsx @@ -5,7 +5,6 @@ import { DangerousButton, OutlinedButton, PrimaryButton, - SecondaryButton, } from "../components/button/button"; import BUTTON_SIZE from "../components/button/button-size"; import BACKGROUND_COLOR from "../components/color/background-color"; @@ -82,7 +81,7 @@ function ViewerButtons({ onEdit }) { function EditingButtons({ onDelete, onDone }) { return ( - - -