diff --git a/src/components/Notification/NotificationList.tsx b/src/components/Notification/NotificationList.tsx new file mode 100644 index 0000000..d11904e --- /dev/null +++ b/src/components/Notification/NotificationList.tsx @@ -0,0 +1,77 @@ +import { If } from '@/system/utils/If'; +import { useGetNotificationList } from './apis/useGetNotificationList'; +import { Icon } from '@/system/components'; +import { Spacing } from '@/system/utils/Spacing'; +import { dday, formatToYYMMDD } from '@/utils/date'; +import { cn } from '@/utils/tailwind-util'; +import { useRouter } from 'next/navigation'; +import { NotificationType } from '@/types/notification'; +import { useNotificationContext } from './context'; + +export function NotificationList() { + const router = useRouter(); + const { close } = useNotificationContext(); + + const { data: notificationList } = useGetNotificationList(); + + const getDateText = (date: string) => { + if (dday(date) >= 0) { + return '오늘'; + } + + return formatToYYMMDD(date, { separator: '.' }); + }; + + const handleNotificationClick = (notification: NotificationType) => { + router.push(`/my-recruit/${notification.referenceId}`); + close(); + }; + + return ( + <> + +
+
+ +
+

지금은 알림이 없어요

+

알림은 30일 뒤에 자동으로 사라져요

+
+ +
+
+
+ 0}> + + {notificationList?.map((notification) => ( +
+
+
+ +
+ +
+ handleNotificationClick(notification)}> + {notification.title} + + + + + + + {notification.message} +
+
+
+ {getDateText(notification.createdAt)} +
+
+
+
+ ))} + + + ); +} diff --git a/src/components/Notification/NotificationWindow.tsx b/src/components/Notification/NotificationWindow.tsx index 32f8df6..deed97f 100644 --- a/src/components/Notification/NotificationWindow.tsx +++ b/src/components/Notification/NotificationWindow.tsx @@ -5,7 +5,8 @@ import { TouchButton } from '../TouchButton'; import { color } from '@/system/token/color'; import { useNotificationContext } from './context'; import { AnimatePresence, motion } from 'framer-motion'; -import { Spacing } from '@/system/utils/Spacing'; +import { NotificationList } from './NotificationList'; +import { AsyncBoundaryWithQuery } from '@/lib'; export function NotificationWindow() { const { isOpen, close } = useNotificationContext(); @@ -24,16 +25,11 @@ export function NotificationWindow() {
-
-
- -
-

지금은 알림이 없어요

-

알림은 30일 뒤에 자동으로 사라져요

-
-
+
+ + +
- )} diff --git a/src/components/Notification/apis/useGetNotificationCount.ts b/src/components/Notification/apis/useGetNotificationCount.ts new file mode 100644 index 0000000..55f76c6 --- /dev/null +++ b/src/components/Notification/apis/useGetNotificationCount.ts @@ -0,0 +1,23 @@ +import { useQuery } from '@tanstack/react-query'; +import { http } from '@/apis/http'; + +export const GET_NOTIFICATION_COUNT = 'notification-count'; + +interface GetNotificationCountResponse { + number: number; +} + +const getNotificationCount = () => { + return http.get({ url: `/notifications/num` }); +}; + +export const useGetNotificationCount = () => { + return useQuery({ + queryKey: [GET_NOTIFICATION_COUNT], + queryFn: async () => { + const res = await getNotificationCount(); + + return res.data; + }, + }); +}; diff --git a/src/components/Notification/apis/useGetNotificationList.ts b/src/components/Notification/apis/useGetNotificationList.ts new file mode 100644 index 0000000..9b4efeb --- /dev/null +++ b/src/components/Notification/apis/useGetNotificationList.ts @@ -0,0 +1,22 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; +import { NotificationType } from '@/types/notification'; +import { http } from '@/apis/http'; + +export const GET_NOTIFICATION_LIST = 'notification-list'; + +type GetNotificationListResponse = NotificationType[]; + +const getNotificationList = () => { + return http.get({ url: `/notifications` }); +}; + +export const useGetNotificationList = () => { + return useSuspenseQuery({ + queryKey: [GET_NOTIFICATION_LIST], + queryFn: async () => { + const res = await getNotificationList(); + + return res.data; + }, + }); +}; diff --git a/src/components/Notification/apis/usePutNotificationRead.ts b/src/components/Notification/apis/usePutNotificationRead.ts new file mode 100644 index 0000000..b47e618 --- /dev/null +++ b/src/components/Notification/apis/usePutNotificationRead.ts @@ -0,0 +1,20 @@ +import { http } from '@/apis/http'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { GET_NOTIFICATION_COUNT } from './useGetNotificationCount'; +import { GET_NOTIFICATION_LIST } from './useGetNotificationList'; + +const putNotificationRead = () => { + return http.put({ url: `/notifications` }); +}; + +export const usePutNotificationRead = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: putNotificationRead, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [GET_NOTIFICATION_COUNT] }); + queryClient.invalidateQueries({ queryKey: [GET_NOTIFICATION_LIST] }); + }, + }); +}; diff --git a/src/components/Notification/context.tsx b/src/components/Notification/context.tsx index 76fcd9a..d049f00 100644 --- a/src/components/Notification/context.tsx +++ b/src/components/Notification/context.tsx @@ -2,6 +2,7 @@ import { generateContext } from '@/lib'; import { useState } from 'react'; +import { usePutNotificationRead } from './apis/usePutNotificationRead'; interface NotificationContext { isOpen: boolean; @@ -17,11 +18,16 @@ const [NotificationWrapper, useNotificationContext] = generateContext setIsOpen(true); - const close = () => setIsOpen(false); + const close = () => { + setIsOpen(false); + readNotification(); + }; - const toggle = () => setIsOpen((prev) => !prev); + const toggle = () => (isOpen ? close() : open()); return ( diff --git a/src/container/Sidebar/Sidebar.tsx b/src/container/Sidebar/Sidebar.tsx index 546c244..90012c1 100644 --- a/src/container/Sidebar/Sidebar.tsx +++ b/src/container/Sidebar/Sidebar.tsx @@ -18,6 +18,7 @@ import { PropsWithChildren, useState } from 'react'; import { Collapsible } from './Collapsible/Collapsible'; import { useNotificationContext } from '@/components/Notification/context'; import { LogoOnlyLeaf } from '@/components/LogoOnlyLeaf'; +import { useGetNotificationCount } from '@/components/Notification/apis/useGetNotificationCount'; export function Sidebar() { const router = useRouter(); @@ -28,6 +29,7 @@ export function Sidebar() { const { isOpen: isNotificationOpen, toggle: toggleNotification } = useNotificationContext(); + const { data: notificationCount } = useGetNotificationCount(); const { data: typeCounts } = useGetCardTypeCount(); const { data: recruiteTitles } = useGetRecruitTitles(); @@ -86,10 +88,15 @@ export function Sidebar() { + {notificationCount?.number || '0'} +
+ } onClick={toggleNotification} /> {/* */} diff --git a/src/system/components/Icon/Icon.tsx b/src/system/components/Icon/Icon.tsx index 55b0131..0ef40b9 100644 --- a/src/system/components/Icon/Icon.tsx +++ b/src/system/components/Icon/Icon.tsx @@ -50,6 +50,8 @@ import Warning from './SVG/Warning'; import { WorkFill } from './SVG/WorkFill'; import { X } from './SVG/X'; import { IllustAlarm } from './SVG/IllustAlarm'; +import { PageOpen } from './SVG/PageOpen'; +import { BellWithRedDot } from './SVG/BellWithRedDot'; import { Backspace } from './SVG/Backspace'; import { SavingSuccess } from './SVG/SavingSuccess'; @@ -105,6 +107,8 @@ const iconMap = { announcementFolder: AnnouncementFolder, IllustAlarm: IllustAlarm, warning: Warning, + pageOpen: PageOpen, + bellWithRedDot: BellWithRedDot, backspace: Backspace, savingSuccess: SavingSuccess, } as const; diff --git a/src/system/components/Icon/SVG/BellWithRedDot.tsx b/src/system/components/Icon/SVG/BellWithRedDot.tsx new file mode 100644 index 0000000..5d58b2e --- /dev/null +++ b/src/system/components/Icon/SVG/BellWithRedDot.tsx @@ -0,0 +1,20 @@ +import { IconBaseType } from './type'; + +export function BellWithRedDot({ size, color }: IconBaseType) { + return ( + + + + + + + ); +} diff --git a/src/system/components/Icon/SVG/PageOpen.tsx b/src/system/components/Icon/SVG/PageOpen.tsx new file mode 100644 index 0000000..e97564e --- /dev/null +++ b/src/system/components/Icon/SVG/PageOpen.tsx @@ -0,0 +1,8 @@ +export function PageOpen() { + return ( + + + + + ); +} diff --git a/src/types/notification.ts b/src/types/notification.ts new file mode 100644 index 0000000..e92f362 --- /dev/null +++ b/src/types/notification.ts @@ -0,0 +1,9 @@ +export interface NotificationType { + id: number; + title: string; + message: string; + isRead: boolean; + type: string; + referenceId: number; + createdAt: string; +}