Skip to content

Commit 09d45e5

Browse files
authored
[FEAT] 알림 기능 추가 (#108) (#109)
* [FEAT] 알림 기능 추가 (#108) * fix: overlaymenu 컴포넌트 추가 * feat: 알림 조회 기능 추가 * feat: 알림 기능 추가 * fix: 모바일 스타일 수정, 읽은거 다시 안읽어지게 수정 (#110) * fix: overlaymenu 컴포넌트 추가 * feat: 알림 조회 기능 추가 * feat: 알림 기능 추가 * fix: 모바일 스타일 수정, 읽은거 다시 안읽어지게 수정
1 parent 856c7ce commit 09d45e5

File tree

11 files changed

+310
-179
lines changed

11 files changed

+310
-179
lines changed

src/api/notify.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import axios from 'axios';
2+
import { getHeaderRefreshTokenConfig } from 'utils/auth';
3+
4+
const PREFIX_URL = '/api/v1/notify';
5+
6+
/**
7+
* notifyId에 해당하는 알림을 읽음처리 한다.
8+
*/
9+
export function readNotification(notifyId) {
10+
return axios.put(
11+
`${PREFIX_URL}/read`,
12+
{ id: notifyId },
13+
getHeaderRefreshTokenConfig(),
14+
);
15+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React, { useCallback } from 'react';
2+
import {
3+
Container,
4+
Message,
5+
NoNotification,
6+
Notification,
7+
NotifyType,
8+
Read,
9+
} from './style';
10+
import { isLoginUser } from 'utils/auth';
11+
import fetcher from 'utils/fetcher';
12+
import { notificationType } from 'utils/notification';
13+
import { NOTIFY_PREFIX_URL, USER_PREFIX_URL } from 'utils/constants';
14+
import useSWR from 'swr';
15+
import { toast } from 'react-toastify';
16+
import { readNotification } from 'api/notify';
17+
import { useNavigate } from 'react-router-dom';
18+
19+
function NotificationPopup() {
20+
const { data: loginUser } = useSWR(
21+
isLoginUser() ? `${USER_PREFIX_URL}/auth/parse/boj` : '',
22+
fetcher,
23+
);
24+
const { data: notificationList, mutate } = useSWR(
25+
`${NOTIFY_PREFIX_URL}/search/receiver?receiver=${loginUser.claim}`,
26+
fetcher,
27+
);
28+
const { mutate: mutateNotificationCount } = useSWR(
29+
loginUser ? `${NOTIFY_PREFIX_URL}/search/unread/count` : null,
30+
fetcher,
31+
);
32+
const readNotificationProc = useCallback(async (id) => {
33+
try {
34+
await readNotification(id);
35+
mutate();
36+
mutateNotificationCount();
37+
} catch {
38+
toast.error('알림을 읽는데 문제가 발생하였습니다.');
39+
}
40+
}, []);
41+
42+
const navigate = useNavigate();
43+
const hasNotification = notificationList?.length > 0;
44+
45+
return (
46+
<Container fixHeight={!hasNotification}>
47+
{!hasNotification && (
48+
<NoNotification>도착한 알림이 없습니다.</NoNotification>
49+
)}
50+
{notificationList?.map((notification) => (
51+
<Notification
52+
key={notification.id}
53+
isRead={notification.isRead}
54+
onClick={() => {
55+
if (notification.relatedBoardId) {
56+
navigate(`/board/${notification.relatedBoardId}`);
57+
}
58+
if (notification.isRead) return;
59+
readNotificationProc(notification.id);
60+
}}
61+
>
62+
<NotifyType>{notificationType[notification.type].label}</NotifyType>
63+
<Message>{notification.message}</Message>
64+
<Read>{notification.isRead ? '읽음' : ''}</Read>
65+
</Notification>
66+
))}
67+
</Container>
68+
);
69+
}
70+
71+
export default NotificationPopup;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import styled from '@emotion/styled';
2+
3+
export const Container = styled.div`
4+
position: fixed;
5+
top: 50px;
6+
right: 70px;
7+
width: 30rem;
8+
max-width: 75%;
9+
max-height: 25rem;
10+
height: ${({ fixHeight }) => (fixHeight ? '25rem' : 'auto')};
11+
/* border-radius: 10px; */
12+
border: 1px solid var(--color-border);
13+
background-color: #fff;
14+
z-index: 2999;
15+
overflow: scroll;
16+
-ms-overflow-style: none; /* IE and Edge */
17+
scrollbar-width: none; /* Firefox */
18+
&::-webkit-scrollbar {
19+
display: none; /* Chrome, Safari, Opera*/
20+
}
21+
@media all and (max-width: 520px) {
22+
width: 95%;
23+
max-width: 100%;
24+
height: 100vh;
25+
right: 5px;
26+
}
27+
`;
28+
29+
export const Notification = styled.div`
30+
display: flex;
31+
flex-direction: column;
32+
gap: 0.5rem;
33+
border-bottom: 1px solid #e0e0e0;
34+
padding: 1.5rem 1rem;
35+
position: relative;
36+
cursor: pointer;
37+
${(props) => props.isRead && 'background-color: var(--color-button-gray);'}
38+
:hover {
39+
background-color: var(--color-button-gray);
40+
}
41+
:last-of-type {
42+
border-bottom: none;
43+
}
44+
`;
45+
46+
export const NotifyType = styled.div`
47+
font-size: 0.8rem;
48+
font-weight: 600;
49+
color: var(--color-primary);
50+
`;
51+
52+
export const Message = styled.div`
53+
font-weight: 400;
54+
font-size: 0.9rem;
55+
`;
56+
57+
export const Read = styled.div`
58+
position: absolute;
59+
top: 1rem;
60+
right: 1rem;
61+
font-size: 0.7rem;
62+
font-weight: 400 !important;
63+
color: var(--color-text-gray);
64+
`;
65+
66+
export const NoNotification = styled.div`
67+
display: flex;
68+
align-items: center;
69+
justify-content: center;
70+
font-weight: 400;
71+
font-size: 0.9rem;
72+
width: 100%;
73+
height: 100%;
74+
`;

src/components/BoardTable/index.jsx

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/components/BoardTable/style.jsx

Lines changed: 0 additions & 57 deletions
This file was deleted.

src/components/OverlayMenu.jsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
export default function OverlayMenu({ children, onClose }) {
4+
const popupRef = useRef(null);
5+
useEffect(() => {
6+
const handler = (e) => {
7+
if (!popupRef) return;
8+
const { current } = popupRef;
9+
// 클릭한 요소가 팝업이 아닐때 팝업을 닫는다.
10+
if (!current || current.contains(e?.target || null)) {
11+
return;
12+
}
13+
onClose(e);
14+
};
15+
document.addEventListener('mouseup', handler);
16+
return () => {
17+
document.removeEventListener('mouseup', handler);
18+
};
19+
}, [onClose]);
20+
return <div ref={popupRef}>{children}</div>;
21+
}

0 commit comments

Comments
 (0)