Skip to content

Commit 80f5446

Browse files
authored
Merge pull request #314 from WeGo-Together/chiyoung-fix/notification-type
[Feat] 알림 Type 최신화 적용, NotificationCard 스토리북 작성
2 parents 39ad82d + 049b094 commit 80f5446

File tree

5 files changed

+375
-39
lines changed

5 files changed

+375
-39
lines changed

.storybook/preview.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,18 @@ const preview: Preview = {
2626
date: /Date$/i,
2727
},
2828
},
29+
nextjs: {
30+
appDirectory: true,
31+
navigation: {
32+
push() {},
33+
replace() {},
34+
prefetch() {},
35+
},
36+
},
2937
},
3038
decorators: [
3139
(Story) => (
32-
<Providers>
40+
<Providers hasRefreshToken={false}>
3341
<Story />
3442
</Providers>
3543
),
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { Meta, StoryObj } from '@storybook/nextjs';
2+
3+
import { mockNotificationItems } from '@/mock/service/notification/notification-mocks';
4+
5+
import { NotificationCard } from '.';
6+
7+
const meta = {
8+
title: 'Components/NotificationCard',
9+
component: NotificationCard,
10+
parameters: {
11+
layout: 'centered',
12+
},
13+
tags: ['autodocs'],
14+
decorators: [
15+
(Story) => (
16+
<div className='w-[400px]'>
17+
<Story />
18+
</div>
19+
),
20+
],
21+
} satisfies Meta<typeof NotificationCard>;
22+
23+
export default meta;
24+
type Story = StoryObj<typeof meta>;
25+
26+
export const Follow: Story = {
27+
args: {
28+
item: mockNotificationItems.find((item) => item.type === 'follow')!,
29+
},
30+
};
31+
32+
export const GroupJoin: Story = {
33+
args: {
34+
item: mockNotificationItems.find((item) => item.type === 'group-join')!,
35+
},
36+
};
37+
38+
export const GroupLeave: Story = {
39+
args: {
40+
item: mockNotificationItems.find((item) => item.type === 'group-leave')!,
41+
},
42+
};
43+
44+
export const GroupCreate: Story = {
45+
args: {
46+
item: mockNotificationItems.find((item) => item.type === 'group-create')!,
47+
},
48+
};
49+
50+
export const GroupDelete: Story = {
51+
args: {
52+
item: mockNotificationItems.find((item) => item.type === 'group-delete')!,
53+
},
54+
};
55+
56+
export const GroupJoinRequest: Story = {
57+
args: {
58+
item: mockNotificationItems.find((item) => item.type === 'group-join-request')!,
59+
},
60+
};
61+
62+
export const GroupJoinApproved: Story = {
63+
args: {
64+
item: mockNotificationItems.find((item) => item.type === 'group-join-approved')!,
65+
},
66+
};
67+
68+
export const GroupJoinRejected: Story = {
69+
args: {
70+
item: mockNotificationItems.find((item) => item.type === 'group-join-rejected')!,
71+
},
72+
};
73+
74+
export const GroupJoinKicked: Story = {
75+
args: {
76+
item: mockNotificationItems.find((item) => item.type === 'group-join-kicked')!,
77+
},
78+
};
79+
80+
export const Unread: Story = {
81+
args: {
82+
item: {
83+
...mockNotificationItems[0],
84+
readAt: '',
85+
},
86+
},
87+
};
88+
89+
export const Read: Story = {
90+
args: {
91+
item: {
92+
...mockNotificationItems[0],
93+
readAt: '2024-01-01T12:00:00Z',
94+
},
95+
},
96+
};
97+
98+
export const AllNotifications: Story = {
99+
args: {
100+
item: mockNotificationItems[0], // 더미 args
101+
},
102+
render: () => (
103+
<div className='flex flex-col gap-0'>
104+
{mockNotificationItems.map((item) => (
105+
<NotificationCard key={item.id} item={item} />
106+
))}
107+
</div>
108+
),
109+
decorators: [
110+
(Story) => (
111+
<div className='w-110'>
112+
<Story />
113+
</div>
114+
),
115+
],
116+
};
Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useRouter } from 'next/navigation';
22

33
import { Icon } from '@/components/icon';
4+
import { Toast } from '@/components/ui';
5+
import { useToast } from '@/components/ui/toast/core';
46
import { useUpdateNotificationRead } from '@/hooks/use-notification/use-notification-update-read';
57
import { formatTimeAgo } from '@/lib/formatDateTime';
68
import { cn } from '@/lib/utils';
@@ -13,17 +15,22 @@ interface Props {
1315
export const NotificationCard = ({ item }: Props) => {
1416
const router = useRouter();
1517
const { mutateAsync } = useUpdateNotificationRead();
18+
const { run } = useToast();
1619

1720
const NotificationIcon = IconMap[item.type];
1821
const title = getTitle(item);
1922
const description = getDescription(item);
2023
const timeAgo = getTimeAgo(item);
24+
const redirectUrl = getRedirectUrl(item);
2125

2226
const handleNotificationClick = () => {
2327
try {
2428
mutateAsync(item.id);
25-
26-
router.push(`${item.redirectUrl}`);
29+
if (redirectUrl) {
30+
router.push(redirectUrl);
31+
} else {
32+
run(<Toast>이미 마감되었거나 삭제된 모임입니다.</Toast>);
33+
}
2734
} catch {}
2835
};
2936

@@ -50,45 +57,91 @@ export const NotificationCard = ({ item }: Props) => {
5057
};
5158

5259
const IconMap: Record<NotificationType, React.ReactNode> = {
53-
FOLLOW: <Icon id='heart' className='text-mint-500 size-6' />,
54-
GROUP_CREATE: <Icon id='map-pin-2' className='size-6 text-[#FFBA1A]' />,
55-
GROUP_DELETE: <Icon id='x-2' className='size-6 text-gray-500' />,
56-
GROUP_JOIN: <Icon id='symbol' className='text-mint-500 size-6' />,
57-
EXIT: <Icon id='x-2' className='size-6 text-gray-500' />,
60+
follow: <Icon id='heart' className='text-mint-500 size-6' />,
61+
'group-join': <Icon id='symbol' className='text-mint-500 size-6' />,
62+
'group-leave': <Icon id='x-2' className='size-6 text-gray-500' />,
63+
'group-create': <Icon id='map-pin-2' className='size-6 text-[#FFBA1A]' />,
64+
'group-delete': <Icon id='x-2' className='size-6 text-gray-500' />,
65+
'group-join-request': <Icon id='send' className='text-mint-500 size-6' />,
66+
'group-join-approved': <Icon id='congratulate' className='size-6' />,
67+
'group-join-rejected': <Icon id='kick' className='size-6' />,
68+
'group-join-kicked': <Icon id='kick' className='size-6' />,
5869
};
5970

6071
const getTitle = (data: NotificationItem) => {
6172
switch (data.type) {
62-
case 'FOLLOW':
73+
case 'follow':
6374
return `새 팔로워`;
64-
case 'GROUP_CREATE':
65-
return `모임 생성`;
66-
case 'GROUP_DELETE':
67-
return `모임 취소`;
68-
case 'GROUP_JOIN':
75+
case 'group-join':
6976
return `모임 현황`;
70-
case 'EXIT':
77+
case 'group-leave':
7178
return `모임 현황`;
79+
case 'group-create':
80+
return `모임 생성`;
81+
case 'group-delete':
82+
return `모임 취소`;
83+
case 'group-join-request':
84+
return `모임 참여 신청`;
85+
case 'group-join-approved':
86+
return `모임 신청 승인`;
87+
case 'group-join-rejected':
88+
return `모임 신청 거절`;
89+
case 'group-join-kicked':
90+
return `모임 강퇴`;
7291
}
7392
};
7493

7594
const getDescription = (data: NotificationItem) => {
76-
// switch (data.type) {
77-
// case 'FOLLOW':
78-
// return `${data.actorNickname} 님이 팔로우 했습니다.`;
79-
// case 'GROUP_CREATE':
80-
// return `${data.actorNickname} 님이 "${data.actorNickname}" 모임을 생성했습니다.`;
81-
// case 'CANCLE':
82-
// return `${data.actorNickname} 님이 "${data.actorNickname}" 모임을 취소했습니다.`;
83-
// case 'ENTER':
84-
// return `${data.actorNickname} 님이 "${data.actorNickname}" 모임에 참가했습니다.`;
85-
// case 'EXIT':
86-
// return `${data.actorNickname} 님이 "${data.actorNickname}" 모임을 떠났습니다.`;
87-
// }
88-
return data.message;
95+
// user type 알림
96+
switch (data.type) {
97+
case 'follow':
98+
return `${data.user.nickname} 님이 팔로우했어요.`;
99+
}
100+
101+
// group type 알림
102+
// 알림 필드 type 변경 전 데이터는 group 필드가 null로 조회되므로 fallback 처리
103+
if (!data.group) return data.message;
104+
105+
// group 필드가 null이 아닐 경우
106+
switch (data.type) {
107+
case 'group-join':
108+
return `${data.user.nickname} 님이 "${data.group.title}" 모임에 참여했어요.`;
109+
case 'group-leave':
110+
return `${data.user.nickname} 님이 "${data.group.title}" 모임을 탈퇴했어요.`;
111+
case 'group-create':
112+
return `${data.user.nickname} 님이 "${data.group.title}" 모임을 생성했어요.`;
113+
case 'group-delete':
114+
return `${data.user.nickname} 님이 "${data.group.title}" 모임을 취소했어요.`;
115+
case 'group-join-request':
116+
return `${data.user.nickname} 님이 "${data.group.title}" 모임에 참여를 요청했어요.`;
117+
case 'group-join-approved':
118+
return `"${data.group.title}" 모임 참여 신청이 승인됐어요.`;
119+
case 'group-join-rejected':
120+
return `"${data.group.title}" 모임 참여 신청이 거절됐어요.`;
121+
case 'group-join-kicked':
122+
return `"${data.group.title}" 모임에서 퇴장됐어요.`;
123+
}
89124
};
90125

91126
const getTimeAgo = (data: NotificationItem) => {
92127
const { createdAt } = data;
93128
return formatTimeAgo(createdAt);
94129
};
130+
131+
const getRedirectUrl = (data: NotificationItem) => {
132+
// user type 알림
133+
switch (data.type) {
134+
case 'follow':
135+
return `/profile/${data.user.id}`;
136+
}
137+
138+
// 알림 필드 type 변경 전 데이터는 group 필드가 null로 조회되므로 fallback 처리
139+
if (!data.group) return null;
140+
141+
switch (data.type) {
142+
case 'group-join-request':
143+
return `/pending/${data.group.id}`;
144+
default:
145+
return `/group/${data.group.id}`;
146+
}
147+
};

0 commit comments

Comments
 (0)