Skip to content

Commit 1e37640

Browse files
authored
[#210] Feat: 알림 페이지 구현 (#214)
1 parent 549ec49 commit 1e37640

File tree

5 files changed

+133
-65
lines changed

5 files changed

+133
-65
lines changed

src/apis/notification.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
3+
import { api } from '@/src/apis/core';
4+
5+
export interface Notification {
6+
type: 'POST' | 'FOLLOW';
7+
followerId?: number;
8+
followerName?: string;
9+
writerId?: number;
10+
writerName?: string;
11+
postId?: number;
12+
timestamp: number;
13+
}
14+
15+
const getNotifications = () => {
16+
return api.get<Notification[]>({
17+
url: '/notifications',
18+
});
19+
};
20+
21+
const INTERVAL_MS = 5000;
22+
23+
export const useGetNotificationsAPI = () => {
24+
const { data } = useQuery({
25+
queryKey: ['notifications'],
26+
queryFn: getNotifications,
27+
refetchInterval: INTERVAL_MS,
28+
});
29+
30+
return data?.body;
31+
};

src/app/notification/_components/NotificationItem.tsx

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,41 @@ import Link from 'next/link';
33

44
import timeOffset from '@/src/utils/timeOffset';
55

6-
type NotificationType = 'post' | 'comment' | 'follow';
7-
8-
interface Notification {
9-
userId: number;
10-
user: string;
11-
type: NotificationType;
12-
createdAt: string;
13-
userImage: string | null;
14-
isNew: boolean;
15-
}
16-
176
interface NotificationItemProps {
18-
notifications: Notification[];
7+
user: string;
8+
userImage: string;
9+
timestamp: number;
10+
userId: number;
11+
content: string;
1912
}
2013

21-
const getNotificationMessage = (type: NotificationType) => {
22-
const notificationString = {
23-
post: ' 님이 새로운 투표를 게시했습니다.',
24-
comment: ' 님이 댓글을 남겼습니다.',
25-
follow: ' 님이 회원님을 팔로우하기 시작했습니다.',
26-
};
27-
28-
return notificationString[type];
29-
};
30-
31-
const NotificationItem = ({ notifications }: NotificationItemProps) => {
14+
const NotificationItem = ({
15+
user,
16+
userImage,
17+
timestamp,
18+
userId,
19+
content,
20+
}: NotificationItemProps) => {
3221
return (
33-
<ul>
34-
{notifications.map(
35-
({ user, userImage, createdAt, type, userId }, index) => {
36-
return (
37-
<li key={index} className='p-4 pt-3'>
38-
<section className='flex items-center gap-2'>
39-
<Link href={`/users/${userId}`}>
40-
<Image
41-
src={userImage ? userImage : '/picky/user-square.svg'}
42-
alt='유저아바타'
43-
width={36}
44-
height={36}
45-
/>
46-
</Link>
47-
48-
<div>
49-
<Link href={`/users/${userId}`}>
50-
<span>{user}</span>
51-
</Link>
52-
<span>{getNotificationMessage(type)}</span>
53-
<p className='font-text-4-rg text-gray-accent4'>
54-
{timeOffset(createdAt)}
55-
</p>
56-
</div>
57-
</section>
58-
</li>
59-
);
60-
},
61-
)}
62-
</ul>
22+
<Link
23+
href={`/users/${userId}`}
24+
className='flex items-center gap-2 p-4 pt-3 hover:bg-gray-accent1'
25+
>
26+
<Image
27+
src={userImage ? userImage : '/picky/user-square.svg'}
28+
alt='유저아바타'
29+
width={36}
30+
height={36}
31+
/>
32+
33+
<div>
34+
<span className='font-title-1-md'>{user}</span>
35+
<span className='font-title-1-md'>{content}</span>
36+
<p className='font-text-4-rg text-gray-accent4'>
37+
{timeOffset(new Date(timestamp - 1000))}
38+
</p>
39+
</div>
40+
</Link>
6341
);
6442
};
6543

src/app/notification/layout.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@
22

33
import { PropsWithChildren } from 'react';
44

5-
import TopBar from '@/src/components/Topbar';
5+
import { useRouter } from 'next/navigation';
6+
7+
import { useGetUserStatusAPI } from '@/src/apis/myInfo';
8+
import GNB from '@/src/components/GNB';
69

710
const NotificationLayout = ({ children }: PropsWithChildren) => {
11+
const router = useRouter();
12+
const { login: isLogin } = useGetUserStatusAPI();
13+
14+
if (!isLogin) {
15+
router.push('/signin');
16+
return null;
17+
}
18+
819
return (
920
<>
10-
<TopBar.Container>
11-
<TopBar.BackButton />
12-
</TopBar.Container>
21+
<span className='font-h2-sm mb-1 p-4'>알림</span>
1322
{children}
23+
<GNB />
1424
</>
1525
);
1626
};

src/app/notification/page.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,52 @@
11
'use client';
22

3-
import DevelopingPage from '@/src/components/DevelopingPage';
3+
import { useGetNotificationsAPI } from '@/src/apis/notification';
4+
import NotificationItem from '@/src/app/notification/_components/NotificationItem';
5+
import EmptyPage from '@/src/components/EmptyPage';
46

5-
const Notification = () => <DevelopingPage />;
7+
const notificationMessageByType = {
8+
POST: ' 님이 게시글을 작성하였습니다.',
9+
FOLLOW: ' 님이 회원님을 팔로우하기 시작했습니다.',
10+
};
11+
12+
const Notification = () => {
13+
const data = useGetNotificationsAPI();
14+
if (!data) return null;
15+
16+
return (
17+
<div className='flex h-full grow flex-col overflow-y-auto'>
18+
{data.length === 0 && (
19+
<EmptyPage text='알림이 없습니다.' enableButton={false} />
20+
)}
21+
{data.map(
22+
(
23+
{ writerId, writerName, followerId, followerName, timestamp, type },
24+
index,
25+
) => (
26+
<li key={`${index}-${type}`} className='list-none'>
27+
{type === 'POST' && (
28+
<NotificationItem
29+
user={writerName!}
30+
userImage='/picky/user-square.svg'
31+
timestamp={timestamp}
32+
userId={writerId!}
33+
content={notificationMessageByType.POST}
34+
/>
35+
)}
36+
{type === 'FOLLOW' && (
37+
<NotificationItem
38+
user={followerName!}
39+
userImage='/picky/user-square.svg'
40+
timestamp={timestamp}
41+
userId={followerId!}
42+
content={notificationMessageByType.FOLLOW}
43+
/>
44+
)}
45+
</li>
46+
),
47+
)}
48+
</div>
49+
);
50+
};
651

752
export default Notification;

src/components/EmptyPage/index.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ interface EmptyPageProps {
66
href?: string;
77
text: string;
88
buttonContent?: string;
9+
enableButton?: boolean;
910
}
1011

1112
const EmptyPage = ({
1213
href = '/',
1314
text,
1415
buttonContent = '투표 글 작성하기',
16+
enableButton = true,
1517
}: EmptyPageProps) => (
1618
<div className='flex h-full grow flex-col items-center justify-center gap-3'>
1719
<Icon id='warning' aria-label='투표 글 없음' size={64} />
1820
<span className='font-h3-bold mb-[28px] text-gray-accent3'>{text}</span>
19-
<Link
20-
href={href}
21-
className='text-h3-sm mt-[28px] flex items-center justify-center rounded-[30px] bg-primary px-6 py-3'
22-
>
23-
{buttonContent}
24-
</Link>
21+
{enableButton && (
22+
<Link
23+
href={href}
24+
className='text-h3-sm mt-[28px] flex items-center justify-center rounded-[30px] bg-primary px-6 py-3'
25+
>
26+
{buttonContent}
27+
</Link>
28+
)}
2529
</div>
2630
);
2731

0 commit comments

Comments
 (0)