Skip to content

Commit

Permalink
[FEAT] 알림 기능 추가 (#108) (#109)
Browse files Browse the repository at this point in the history
* [FEAT] 알림 기능 추가  (#108)

* fix: overlaymenu 컴포넌트 추가

* feat: 알림 조회 기능 추가

* feat: 알림 기능 추가

* fix: 모바일 스타일 수정, 읽은거 다시 안읽어지게 수정 (#110)

* fix: overlaymenu 컴포넌트 추가

* feat: 알림 조회 기능 추가

* feat: 알림 기능 추가

* fix: 모바일 스타일 수정, 읽은거 다시 안읽어지게 수정
  • Loading branch information
klloo authored May 26, 2024
1 parent 856c7ce commit 09d45e5
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 179 deletions.
15 changes: 15 additions & 0 deletions src/api/notify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import axios from 'axios';
import { getHeaderRefreshTokenConfig } from 'utils/auth';

const PREFIX_URL = '/api/v1/notify';

/**
* notifyId에 해당하는 알림을 읽음처리 한다.
*/
export function readNotification(notifyId) {
return axios.put(
`${PREFIX_URL}/read`,
{ id: notifyId },
getHeaderRefreshTokenConfig(),
);
}
71 changes: 71 additions & 0 deletions src/components/NotificationPopup/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useCallback } from 'react';
import {
Container,
Message,
NoNotification,
Notification,
NotifyType,
Read,
} from './style';
import { isLoginUser } from 'utils/auth';
import fetcher from 'utils/fetcher';
import { notificationType } from 'utils/notification';
import { NOTIFY_PREFIX_URL, USER_PREFIX_URL } from 'utils/constants';
import useSWR from 'swr';
import { toast } from 'react-toastify';
import { readNotification } from 'api/notify';
import { useNavigate } from 'react-router-dom';

function NotificationPopup() {
const { data: loginUser } = useSWR(
isLoginUser() ? `${USER_PREFIX_URL}/auth/parse/boj` : '',
fetcher,
);
const { data: notificationList, mutate } = useSWR(
`${NOTIFY_PREFIX_URL}/search/receiver?receiver=${loginUser.claim}`,
fetcher,
);
const { mutate: mutateNotificationCount } = useSWR(
loginUser ? `${NOTIFY_PREFIX_URL}/search/unread/count` : null,
fetcher,
);
const readNotificationProc = useCallback(async (id) => {
try {
await readNotification(id);
mutate();
mutateNotificationCount();
} catch {
toast.error('알림을 읽는데 문제가 발생하였습니다.');
}
}, []);

const navigate = useNavigate();
const hasNotification = notificationList?.length > 0;

return (
<Container fixHeight={!hasNotification}>
{!hasNotification && (
<NoNotification>도착한 알림이 없습니다.</NoNotification>
)}
{notificationList?.map((notification) => (
<Notification
key={notification.id}
isRead={notification.isRead}
onClick={() => {
if (notification.relatedBoardId) {
navigate(`/board/${notification.relatedBoardId}`);
}
if (notification.isRead) return;
readNotificationProc(notification.id);
}}
>
<NotifyType>{notificationType[notification.type].label}</NotifyType>
<Message>{notification.message}</Message>
<Read>{notification.isRead ? '읽음' : ''}</Read>
</Notification>
))}
</Container>
);
}

export default NotificationPopup;
74 changes: 74 additions & 0 deletions src/components/NotificationPopup/style.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import styled from '@emotion/styled';

export const Container = styled.div`
position: fixed;
top: 50px;
right: 70px;
width: 30rem;
max-width: 75%;
max-height: 25rem;
height: ${({ fixHeight }) => (fixHeight ? '25rem' : 'auto')};
/* border-radius: 10px; */
border: 1px solid var(--color-border);
background-color: #fff;
z-index: 2999;
overflow: scroll;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera*/
}
@media all and (max-width: 520px) {
width: 95%;
max-width: 100%;
height: 100vh;
right: 5px;
}
`;

export const Notification = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
border-bottom: 1px solid #e0e0e0;
padding: 1.5rem 1rem;
position: relative;
cursor: pointer;
${(props) => props.isRead && 'background-color: var(--color-button-gray);'}
:hover {
background-color: var(--color-button-gray);
}
:last-of-type {
border-bottom: none;
}
`;

export const NotifyType = styled.div`
font-size: 0.8rem;
font-weight: 600;
color: var(--color-primary);
`;

export const Message = styled.div`
font-weight: 400;
font-size: 0.9rem;
`;

export const Read = styled.div`
position: absolute;
top: 1rem;
right: 1rem;
font-size: 0.7rem;
font-weight: 400 !important;
color: var(--color-text-gray);
`;

export const NoNotification = styled.div`
display: flex;
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 0.9rem;
width: 100%;
height: 100%;
`;
44 changes: 0 additions & 44 deletions src/components/BoardTable/index.jsx

This file was deleted.

57 changes: 0 additions & 57 deletions src/components/BoardTable/style.jsx

This file was deleted.

21 changes: 21 additions & 0 deletions src/components/OverlayMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useEffect, useRef } from 'react';

export default function OverlayMenu({ children, onClose }) {
const popupRef = useRef(null);
useEffect(() => {
const handler = (e) => {
if (!popupRef) return;
const { current } = popupRef;
// 클릭한 요소가 팝업이 아닐때 팝업을 닫는다.
if (!current || current.contains(e?.target || null)) {
return;
}
onClose(e);
};
document.addEventListener('mouseup', handler);
return () => {
document.removeEventListener('mouseup', handler);
};
}, [onClose]);
return <div ref={popupRef}>{children}</div>;
}
Loading

0 comments on commit 09d45e5

Please sign in to comment.