diff --git a/src/components/common/EmptyView.tsx b/src/components/common/EmptyView.tsx index 157c1b5d..c84d8e66 100644 --- a/src/components/common/EmptyView.tsx +++ b/src/components/common/EmptyView.tsx @@ -11,7 +11,8 @@ interface IEmptyView { | '내후기' | '예치금내역' | '인증내역' - | '커밋인증'; + | '커밋인증' + | '알림내역'; } export default function EmptyView({ pageType }: IEmptyView) { diff --git a/src/components/home/Notification.tsx b/src/components/home/Notification.tsx index 1c1c08e1..fc3c3448 100644 --- a/src/components/home/Notification.tsx +++ b/src/components/home/Notification.tsx @@ -9,7 +9,6 @@ export default function Notification({ updateKey }: { updateKey: boolean }) { ['hasNewEvent', updateKey], async () => { const response = await newNotificationApi(); - console.log('새 알림 있나요?', response.data.newEvent); return response.data; }, { refetchOnWindowFocus: false }, diff --git a/src/components/notification/NotificationBox.tsx b/src/components/notification/NotificationBox.tsx new file mode 100644 index 00000000..7de64131 --- /dev/null +++ b/src/components/notification/NotificationBox.tsx @@ -0,0 +1,42 @@ +import styled from '@emotion/styled'; +import Image from 'next/image'; +import INotify from '@/types/notify'; + +interface INotificationBox { + notification: INotify; + openDeleteModal: (notificationId: number) => void; +} +function NotificationBox({ notification, openDeleteModal }: INotificationBox) { + return ( + + openDeleteModal(notification.notificationId)} + > + + + {notification.title} + {notification.message} + {notification.createDate} + + ); +} + +const SNotificationBox = styled.div` + border: 1px solid ${({ theme }) => theme.color.gray_3c}; + border-radius: 10px; + margin: 0.625rem; + padding: 0.625rem 0.3125rem; +`; + +const SDeleteIcon = styled(Image)` + cursor: pointer; +`; + +export default NotificationBox; diff --git a/src/components/notification/NotificationModal.tsx b/src/components/notification/NotificationModal.tsx new file mode 100644 index 00000000..1ff68516 --- /dev/null +++ b/src/components/notification/NotificationModal.tsx @@ -0,0 +1,71 @@ +import styled from '@emotion/styled'; +import Portal from '../modal/ModalPortal'; + +interface INotificationModal { + isOpen: boolean; + onClose: () => void; + onDelete: () => void; +} + +export default function NotificationModal({ + isOpen, + onClose, + onDelete, +}: INotificationModal) { + const handleBackgroundClick = (event: React.MouseEvent) => { + if (event.target === event.currentTarget) { + onClose(); + } + }; + + return ( + isOpen && ( + + + + + 삭제하기 + + + 취소하기 + + + + + ) + ); +} + +export const SModalWrapper = styled.div` + z-index: 20; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: flex-end; + background-color: rgba(0, 0, 0, 0.6); +`; + +const SModalContent = styled.div` + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + margin-bottom: 2.5rem; +`; + +const SModalBtn = styled.button<{ deleteBtn: boolean }>` + width: 19.5rem; + height: 48px; + background-color: ${({ theme }) => theme.color.white}; + margin-top: 1rem; + border-radius: 8px; + text-align: center; + font-size: ${({ theme }) => theme.fontSize.subtitle1}; + font-weight: ${({ theme }) => theme.fontWeight.subtitle1}; + color: ${({ theme, deleteBtn }) => + deleteBtn ? theme.color.error : theme.color.normal}; +`; diff --git a/src/constants/emptyText.ts b/src/constants/emptyText.ts index bb37224e..73ed9c43 100644 --- a/src/constants/emptyText.ts +++ b/src/constants/emptyText.ts @@ -27,6 +27,10 @@ const emptyText = { mainText: '나만의 커밋 인증', subText: '1일 1커밋 챌린지는 내 인증 내역만 볼 수 있습니다.', }, + 알림내역: { + mainText: '알림 목록이 비어 있어요', + subText: '최근 2주 동안 받은 알림을 확인할 수 있어요.', + }, }; export default emptyText; diff --git a/src/lib/axios/notification/api.ts b/src/lib/axios/notification/api.ts index f243b817..0f6fb4cb 100644 --- a/src/lib/axios/notification/api.ts +++ b/src/lib/axios/notification/api.ts @@ -25,4 +25,13 @@ const notifyApi = () => { return axiosInstance.get('/notify'); }; -export { subscribeApi, newNotificationApi, notifyApi }; +/** + * 알림 내역 삭제 DELETE + * @param notificationId + * @returns status, message + */ +const deleteNotificationApi = (notificationId: number) => { + return axiosInstance.delete(`/notify/${notificationId}`); +}; + +export { subscribeApi, newNotificationApi, notifyApi, deleteNotificationApi }; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 07f2b255..f4315c22 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -108,10 +108,9 @@ export default function Home({ withCredentials: true, }); - eventSource.addEventListener('event', (event) => { + eventSource.addEventListener('event', () => { openSnackBar('새로운 알림이 있습니다.'); setNotificationUpdate(true); - console.log(event); }); }; @@ -142,7 +141,6 @@ export default function Home({ // eslint-disable-next-line @typescript-eslint/no-misused-promises timeoutId = setTimeout(refreshTokenAndReconnect, 60 * 1000 * 9); } else if (readyReconnect) { - console.log('토큰 만료 시간 전 재발급 및 재연동 시도'); refreshTokenAndReconnect().catch(console.error); } diff --git a/src/pages/notification/index.tsx b/src/pages/notification/index.tsx index f0fc7408..95a337bf 100644 --- a/src/pages/notification/index.tsx +++ b/src/pages/notification/index.tsx @@ -1,12 +1,19 @@ import styled from '@emotion/styled'; -import { useQuery } from '@tanstack/react-query'; -import { v4 as uuidv4 } from 'uuid'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; import Layout from '@/components/common/Layout'; import EmptyView from '@/components/common/EmptyView'; import INotify from '@/types/notify'; -import { notifyApi } from '@/lib/axios/notification/api'; +import { deleteNotificationApi, notifyApi } from '@/lib/axios/notification/api'; +import NotificationBox from '@/components/notification/NotificationBox'; +import NotificationModal from '@/components/notification/NotificationModal'; export default function Notification() { + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedNotificationId, setSelectedNotificationId] = useState< + number | null + >(null); + const queryClient = useQueryClient(); const { data, error } = useQuery( ['notifyList'], async () => { @@ -15,9 +22,29 @@ export default function Notification() { }, { refetchOnWindowFocus: false }, ); + + const deleteMutation = useMutation(deleteNotificationApi, { + onSuccess: () => { + queryClient.invalidateQueries(['notifyList']).catch(console.error); + setIsModalOpen(false); + }, + }); + + const openDeleteModal = (notificationId: number) => { + setSelectedNotificationId(notificationId); + setIsModalOpen(true); + }; + + const handleDelete = () => { + if (selectedNotificationId !== null) { + deleteMutation.mutate(selectedNotificationId); + } + }; + if (error) { console.error('새로운 알림 유무 불러오기 실패', error); } + return ( 0 ? ( {data.map((notify) => ( - - {notify.title} - {notify.message} - {notify.createDate} - + openDeleteModal(notify.notificationId)} + /> ))} ) : ( - + )} + setIsModalOpen(false)} + onDelete={handleDelete} + /> ); } diff --git a/src/types/notify.ts b/src/types/notify.ts index a034df5a..2a6f4ab0 100644 --- a/src/types/notify.ts +++ b/src/types/notify.ts @@ -1,4 +1,5 @@ interface INotify { + notificationId: number; title: string; message: string; createDate: string;
{notification.title}
{notification.message}
{notification.createDate}
{notify.title}
{notify.message}
{notify.createDate}