Skip to content

Commit f2a337b

Browse files
committed
Merge branch 'develop' into feature/chan
2 parents 6645e69 + dc915cd commit f2a337b

File tree

10 files changed

+271
-10
lines changed

10 files changed

+271
-10
lines changed

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</head>
2727
<body>
2828
<div id="root"></div>
29+
<div id="modal-root"></div>
2930
<script type="module" src="/src/main.jsx"></script>
3031
</body>
3132
</html>

src/api/deleteMessage.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { api } from './api';
2+
3+
export default async function deleteMessage(messageId) {
4+
const teamId = import.meta.env.VITE_TEAM_ID;
5+
const res = await api.delete(`/${teamId}/messages/${messageId}/`);
6+
return res.data;
7+
}

src/api/deleteRecipient.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { api } from './api';
2+
3+
export default async function deleteMessage(recipientId) {
4+
const teamId = import.meta.env.VITE_TEAM_ID;
5+
const res = await api.delete(`/${teamId}/recipients/${recipientId}/`);
6+
return res.data;
7+
}

src/components/Card/Card.jsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Badge from '../Badge/Badge';
66
import styles from './Card.module.scss';
77

88
export default function Card({
9+
id,
910
image,
1011
recipientId,
1112
sender,
@@ -14,14 +15,22 @@ export default function Card({
1415
createdAt,
1516
font,
1617
empty = false,
18+
onDelete,
19+
onClick,
1720
}) {
1821
const navigate = useNavigate();
1922
const sanitizedHTML = DOMPurify.sanitize(children);
2023

21-
function handleClick() {
24+
function clickPost() {
2225
navigate(`/post/${recipientId}/message/`);
2326
}
2427

28+
function clickDelete(e) {
29+
e.stopPropagation();
30+
if (confirm('정말 삭제하시겠어요?')) {
31+
onDelete?.(id);
32+
}
33+
}
2534
const fontFamilyMap = {
2635
'Noto Sans': '"Noto Sans", sans-serif',
2736
Pretendard: '"Pretendard", sans-serif',
@@ -30,9 +39,12 @@ export default function Card({
3039
};
3140

3241
return (
33-
<article className={`${styles.card} ${empty ? styles['card--empty'] : ''}`}>
42+
<article
43+
className={`${styles.card} ${empty ? styles['card--empty'] : ''}`}
44+
onClick={() => !empty && onClick?.(id)}
45+
>
3446
{empty ? (
35-
<div onClick={handleClick}>
47+
<div onClick={clickPost}>
3648
<img src={plus} alt="추가하기" />
3749
</div>
3850
) : (
@@ -52,7 +64,7 @@ export default function Card({
5264
</div>
5365
</div>
5466
<div className={styles['card__delete-button']}>
55-
<button>
67+
<button onClick={clickDelete}>
5668
<img src={deleteIcon} alt="쓰레기통 아이콘" />
5769
</button>
5870
</div>

src/components/Card/Card.module.scss

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@
5353
gap: 6px;
5454

5555
.card__profile-name {
56+
display: -webkit-box;
57+
-webkit-box-orient: vertical;
5658
overflow: hidden;
57-
text-overflow: ellipsis;
58-
white-space: nowrap;
59+
-webkit-line-clamp: 1;
60+
line-clamp: 1;
5961
@include font-20-regular;
6062

6163
span {
@@ -79,7 +81,6 @@
7981

8082
&__body {
8183
height: 280px;
82-
padding: 16px 0;
8384

8485
.card__content {
8586
display: -webkit-box;

src/components/Modal/Modal.jsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import DOMPurify from 'dompurify';
2+
import ReactDOM from 'react-dom';
3+
import Badge from '../Badge/Badge';
4+
import Button from '../common/Button';
5+
import styles from './Modal.module.scss';
6+
7+
export default function Modal({
8+
image,
9+
sender,
10+
relationship,
11+
children,
12+
createdAt,
13+
onClose,
14+
}) {
15+
const sanitizedHTML = DOMPurify.sanitize(children);
16+
17+
return ReactDOM.createPortal(
18+
<div className={styles.backdrop}>
19+
<article className={styles.modal}>
20+
<header className={styles['modal__header']}>
21+
<div className={styles['modal__profile-img']}>
22+
<img src={image} alt="프로필 이미지" />
23+
</div>
24+
<div className={styles['modal__user-info']}>
25+
<div className={styles['modal__profile-name']}>
26+
<p>
27+
From. <span>{sender}</span>
28+
</p>
29+
</div>
30+
<div className={styles['modal__relation-badge']}>
31+
<Badge relation={relationship} />
32+
</div>
33+
</div>
34+
<div className={styles['modal__date']}>{createdAt}</div>
35+
</header>
36+
<div className={styles['modal__body']}>
37+
<div className={styles['modal__content']}>
38+
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
39+
</div>
40+
</div>
41+
<div className={styles['modal__footer']}>
42+
<Button
43+
className={styles['modal__button']}
44+
type="confirm"
45+
onClick={onClose}
46+
>
47+
확인
48+
</Button>
49+
</div>
50+
</article>
51+
</div>,
52+
document.getElementById('modal-root'),
53+
);
54+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
@use '../../assets/styles/variables.scss' as *;
2+
3+
.backdrop {
4+
display: flex;
5+
position: fixed;
6+
top: 0;
7+
left: 0;
8+
z-index: 999;
9+
width: 100vw;
10+
height: 100vh;
11+
background-color: rgba(0, 0, 0, 0.6);
12+
justify-content: center;
13+
align-items: center;
14+
}
15+
16+
.modal {
17+
display: flex;
18+
flex-direction: column;
19+
overflow: hidden;
20+
position: fixed;
21+
top: 50%;
22+
left: 50%;
23+
z-index: 1000;
24+
width: 600px;
25+
height: 476px;
26+
padding: 40px;
27+
border-radius: 20px;
28+
background-color: $white;
29+
transform: translate(-50%, -50%);
30+
box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.08);
31+
gap: 16px;
32+
33+
&__header {
34+
display: flex;
35+
justify-content: space-between;
36+
align-items: center;
37+
gap: 16px;
38+
padding-bottom: 16px;
39+
border-bottom: 1px solid $gray-200;
40+
}
41+
42+
&__profile-img {
43+
position: relative;
44+
width: 56px;
45+
height: 56px;
46+
border: 1px solid $gray-200;
47+
border-radius: 100px;
48+
overflow: hidden;
49+
50+
img {
51+
position: absolute;
52+
width: 100%;
53+
height: 100%;
54+
object-fit: cover;
55+
}
56+
}
57+
58+
&__user-info {
59+
display: flex;
60+
flex-direction: column;
61+
flex: 1;
62+
gap: 6px;
63+
}
64+
65+
&__profile-name {
66+
display: -webkit-box;
67+
overflow: hidden;
68+
@include font-20-regular;
69+
-webkit-box-orient: vertical;
70+
-webkit-line-clamp: 1;
71+
line-clamp: 1;
72+
73+
span {
74+
@include font-20-bold;
75+
}
76+
}
77+
78+
&__date {
79+
@include font-14-regular;
80+
color: $gray-400;
81+
}
82+
83+
&__body {
84+
width: 500px;
85+
height: 240px;
86+
}
87+
88+
&__content {
89+
@include font-18-regular;
90+
color: $gray-500;
91+
word-break: break-word;
92+
}
93+
94+
&__footer {
95+
padding-top: 8px;
96+
margin: 0 auto;
97+
}
98+
99+
&__button {
100+
align-self: center;
101+
}
102+
}

src/components/common/Button.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
}
5555

5656
&--delete {
57-
width: 9.2rem;
57+
width: 10rem;
5858
height: 3.9rem;
5959
padding: 0.7rem 1.6rem;
6060
border-radius: 6px;

src/pages/Recipient/Recipient.jsx

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { useParams } from 'react-router';
1+
import { useParams, useNavigate } from 'react-router';
22
import { useEffect, useState, useRef } from 'react';
33
import getRecipient from '../../api/getRecipient';
44
import getMessages from '../../api/getMessages';
5+
import deleteMessage from '../../api/deleteMessage.js';
6+
import deleteRecipient from '../../api/deleteRecipient.js';
57
import HeaderService from '../../components/recipient/HeaderService/HeaderService';
68
import Card from '../../components/Card/Card';
9+
import Button from '../../components/common/Button';
10+
import Modal from '../../components/Modal/Modal.jsx';
711
import styles from './Recipient.module.scss';
812

913
export default function Recipient() {
@@ -13,7 +17,9 @@ export default function Recipient() {
1317
const [offset, setOffset] = useState(0);
1418
const [loading, setLoading] = useState(false);
1519
const [hasNextMessage, setHasNextMessage] = useState(false);
20+
const [selectedCardId, setSelectedCardId] = useState(null);
1621
const observerRef = useRef();
22+
const navigate = useNavigate();
1723

1824
useEffect(() => {
1925
const fetchRecipient = async () => {
@@ -71,6 +77,42 @@ export default function Recipient() {
7177
setOffset((prev) => prev + limit);
7278
};
7379

80+
async function handleDeleteMessage(id) {
81+
try {
82+
await deleteMessage(id);
83+
setMessages((prevMessages) =>
84+
prevMessages.filter((msg) => msg.id !== id),
85+
);
86+
} catch (error) {
87+
console.error('삭제 실패:', error);
88+
}
89+
}
90+
91+
async function handleDeleteRecipient(id) {
92+
try {
93+
const firstConfirm = confirm('정말 이 페이지를 삭제하시겠어요?');
94+
if (!firstConfirm) return;
95+
const secondConfirm = confirm(
96+
'정말 정말 삭제하시겠어요? 되돌릴 수 없어요!',
97+
);
98+
if (!secondConfirm) return;
99+
await deleteRecipient(id);
100+
navigate('/');
101+
} catch (error) {
102+
console.error('삭제 실패:', error);
103+
}
104+
}
105+
106+
function handleOpenModal(id) {
107+
setSelectedCardId(id);
108+
}
109+
110+
function handleCloseModal() {
111+
setSelectedCardId(null);
112+
}
113+
114+
const selectedCard = messages.find((card) => card.id === selectedCardId);
115+
74116
if (!postData || messages.length < 0) return <div>Loading...</div>;
75117

76118
return (
@@ -84,20 +126,47 @@ export default function Recipient() {
84126
: {}
85127
}
86128
>
129+
<div className={styles['button-container']}>
130+
<Button
131+
className={styles['delete-button']}
132+
type="delete"
133+
onClick={() => handleDeleteRecipient(id)}
134+
>
135+
삭제하기
136+
</Button>
137+
</div>
87138
<div className={styles['card-container']}>
88139
<Card recipientId={id} empty={true} />
89140
{messages.map((msg) => (
90141
<Card
91142
key={msg.id}
143+
id={msg.id}
92144
image={msg.profileImageURL}
93145
sender={msg.sender}
94146
relationship={msg.relationship}
95147
createdAt={msg.createdAt.slice(0, 10).split('-').join('.')}
148+
onDelete={handleDeleteMessage}
149+
onClick={() => handleOpenModal(msg.id)}
96150
font={msg.font}
97151
>
98152
{msg.content}
99153
</Card>
100154
))}
155+
{selectedCardId && (
156+
<Modal
157+
key={selectedCard.id}
158+
image={selectedCard.profileImageURL}
159+
sender={selectedCard.sender}
160+
relationship={selectedCard.relationship}
161+
createdAt={selectedCard.createdAt
162+
.slice(0, 10)
163+
.split('-')
164+
.join('.')}
165+
onClose={handleCloseModal}
166+
>
167+
{selectedCard.content}
168+
</Modal>
169+
)}
101170
</div>
102171
{hasNextMessage && <div ref={observerRef} className="load"></div>}
103172
</div>

0 commit comments

Comments
 (0)