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;
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/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..1400b3c 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);
+ };
+
return (
-
+
diff --git a/src/features/message/components/messages-grid.jsx b/src/features/message/components/messages-grid.jsx
index 0e42b5e..7aae610 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 handleDeleteClick = (messageId) => {
- onDelete(messageId);
+ const handleMessageClick = (message) => {
+ setShowsModal(true);
+ setModalMessage(message);
};
- const messageCard = (message) => (
- handleDeleteClick(message.id)}
- />
- );
+ const handleDeleteClick = (message) => {
+ onDelete(message);
+ };
+
+ const handleModalConfirm = () => {
+ setShowsModal(false);
+ setModalMessage(null);
+ };
return (
-
-
- {messages.map((message) =>
- isEditing ? (
- messageCard(message)
- ) : (
-
-
-
- )
- )}
-
-
+ <>
+
+
+ {messages.map((message) => (
+
+ ))}
+
+
+
+
+
+ >
);
}
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 };
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 };
}
diff --git a/src/pages/messages-page.jsx b/src/pages/messages-page.jsx
index 5584178..41030f0 100644
--- a/src/pages/messages-page.jsx
+++ b/src/pages/messages-page.jsx
@@ -8,6 +8,8 @@ import {
} 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 +22,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";
@@ -75,19 +78,15 @@ function ViewerButtons({ onEdit }) {
);
}
-function EditingButtons({ onDelete, onCancel }) {
+function EditingButtons({ onDelete, onDone }) {
return (
-
-
+
);
}
@@ -99,6 +98,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 +116,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 handleEditCancel = () => {
+ 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 () => {
@@ -186,7 +215,7 @@ function MessagesPage() {
{isEditing ? (
) : (
@@ -204,7 +233,33 @@ function MessagesPage() {
>
);
- return isMobile ? content : {content};
+ return isMobile ? (
+ content
+ ) : (
+
+ {content}
+
+
+
+
+ >
+ }
+ />
+
+
+ );
}
export default MessagesPage;
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.
+