Skip to content

Commit

Permalink
feat: 알림 기능 구현 (#85)
Browse files Browse the repository at this point in the history
* 알림

* fix

* icon

* query

* notification

* routing
  • Loading branch information
woo-jk authored Sep 11, 2024
1 parent 7f2586e commit e5d40ed
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 13 deletions.
77 changes: 77 additions & 0 deletions src/components/Notification/NotificationList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<If condition={notificationList?.length === 0}>
<div className="flex h-full items-center justify-center">
<div className="flex flex-col gap-24">
<Icon name="IllustAlarm" />
<div className="flex flex-col items-center gap-4">
<h2 className="text-neutral-10 text-body1 font-normal">지금은 알림이 없어요</h2>
<p className="text-caption2 font-regular text-neutral-35">알림은 30일 뒤에 자동으로 사라져요</p>
</div>
<Spacing size={100} />
</div>
</div>
</If>
<If condition={notificationList && notificationList.length > 0}>
<Spacing size={40} />
{notificationList?.map((notification) => (
<div key={notification.id}>
<div className={cn('flex flex-col gap-8 py-8', notification.isRead && 'opacity-30')}>
<div className="flex gap-8">
<If condition={!notification.isRead}>
<div className="flex-shrink-0 w-6 h-6 mt-6 rounded-full bg-red-40" />
</If>
<div className="text-white text-label1">
<span
className="font-semibold underline cursor-pointer"
onClick={() => handleNotificationClick(notification)}>
{notification.title}
<span className="relative">
<span className="absolute top-1 left-2">
<Icon name="pageOpen" />
</span>
</span>
</span>
<span className="ml-20">{notification.message}</span>
</div>
</div>
<div className="flex justify-end text-caption2 font-regular text-neutral-35">
{getDateText(notification.createdAt)}
</div>
</div>
<div className="w-full h-[1px] my-16 bg-neutral-75" />
</div>
))}
</If>
</>
);
}
16 changes: 6 additions & 10 deletions src/components/Notification/NotificationWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -24,16 +25,11 @@ export function NotificationWindow() {
<Icon name="x" color={color.neutral40} />
</TouchButton>
</div>
<div className="flex-1 flex items-center justify-center">
<div className="flex flex-col gap-24">
<Icon name="IllustAlarm" />
<div className="flex flex-col items-center gap-4">
<h2 className="text-neutral-10 text-body1 font-normal">지금은 알림이 없어요</h2>
<p className="text-caption2 font-regular text-neutral-35">알림은 30일 뒤에 자동으로 사라져요</p>
</div>
</div>
<div className="flex-1 overflow-auto">
<AsyncBoundaryWithQuery>
<NotificationList />
</AsyncBoundaryWithQuery>
</div>
<Spacing size={100} />
</motion.div>
)}
</AnimatePresence>
Expand Down
23 changes: 23 additions & 0 deletions src/components/Notification/apis/useGetNotificationCount.ts
Original file line number Diff line number Diff line change
@@ -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<GetNotificationCountResponse>({ url: `/notifications/num` });
};

export const useGetNotificationCount = () => {
return useQuery({
queryKey: [GET_NOTIFICATION_COUNT],
queryFn: async () => {
const res = await getNotificationCount();

return res.data;
},
});
};
22 changes: 22 additions & 0 deletions src/components/Notification/apis/useGetNotificationList.ts
Original file line number Diff line number Diff line change
@@ -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<GetNotificationListResponse>({ url: `/notifications` });
};

export const useGetNotificationList = () => {
return useSuspenseQuery({
queryKey: [GET_NOTIFICATION_LIST],
queryFn: async () => {
const res = await getNotificationList();

return res.data;
},
});
};
20 changes: 20 additions & 0 deletions src/components/Notification/apis/usePutNotificationRead.ts
Original file line number Diff line number Diff line change
@@ -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] });
},
});
};
10 changes: 8 additions & 2 deletions src/components/Notification/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { generateContext } from '@/lib';
import { useState } from 'react';
import { usePutNotificationRead } from './apis/usePutNotificationRead';

interface NotificationContext {
isOpen: boolean;
Expand All @@ -17,11 +18,16 @@ const [NotificationWrapper, useNotificationContext] = generateContext<Notificati
function NotificatinProvider({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);

const { mutate: readNotification } = usePutNotificationRead();

const open = () => setIsOpen(true);

const close = () => setIsOpen(false);
const close = () => {
setIsOpen(false);
readNotification();
};

const toggle = () => setIsOpen((prev) => !prev);
const toggle = () => (isOpen ? close() : open());

return (
<NotificationWrapper isOpen={isOpen} open={open} close={close} toggle={toggle}>
Expand Down
9 changes: 8 additions & 1 deletion src/container/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();

Expand Down Expand Up @@ -86,10 +88,15 @@ export function Sidebar() {
</Dialog.Content>
</Dialog>
<SidebarButton
iconName="bell"
iconName={!!notificationCount?.number ? 'bellWithRedDot' : 'bell'}
selected={isNotificationOpen}
expanded={expanded}
expandedText="알림"
right={
<div className="px-4 rounded-3 bg-neutral-80 text-neutral-35 text-caption1 font-medium">
{notificationCount?.number || '0'}
</div>
}
onClick={toggleNotification}
/>
{/* <SidebarButton iconName="memo" selected={false} expanded={expanded} expandedText="메모 모아보기" /> */}
Expand Down
4 changes: 4 additions & 0 deletions src/system/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -105,6 +107,8 @@ const iconMap = {
announcementFolder: AnnouncementFolder,
IllustAlarm: IllustAlarm,
warning: Warning,
pageOpen: PageOpen,
bellWithRedDot: BellWithRedDot,
backspace: Backspace,
savingSuccess: SavingSuccess,
} as const;
Expand Down
20 changes: 20 additions & 0 deletions src/system/components/Icon/SVG/BellWithRedDot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IconBaseType } from './type';

export function BellWithRedDot({ size, color }: IconBaseType) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none">
<path
d="M5.81186 8.93778C5.81186 5.52036 8.58222 2.75 11.9996 2.75C15.417 2.75 18.1874 5.52036 18.1874 8.93777V13.5184L19.8 17.8572H4.19922L5.81186 13.5184V8.93778Z"
stroke={color}
strokeWidth="1.5"
/>
<path
d="M15.1673 17.8574V18.0834C15.1673 19.8323 13.7496 21.2501 12.0007 21.2501C10.2517 21.2501 8.83398 19.8323 8.83398 18.0834V17.8574"
stroke={color}
strokeWidth="1.5"
/>
<circle cx="22" cy="2" r="2" fill="#FF6C62" />
<circle cx="22" cy="2" r="2" fill="#FF6C62" />
</svg>
);
}
8 changes: 8 additions & 0 deletions src/system/components/Icon/SVG/PageOpen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function PageOpen() {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6654 9.66675V4.33341H6.33203" stroke="white" strokeWidth="1.25" strokeLinecap="square" />
<path d="M11 5L3 13" stroke="white" strokeWidth="1.25" strokeLinecap="square" />
</svg>
);
}
9 changes: 9 additions & 0 deletions src/types/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface NotificationType {
id: number;
title: string;
message: string;
isRead: boolean;
type: string;
referenceId: number;
createdAt: string;
}

0 comments on commit e5d40ed

Please sign in to comment.