Merge branch 'fe/refactor/fcm' into 'fe/develop'

Refactor: FCM 토큰 재등록 로직 추가, 알림 컴포넌트 디자인 개선 - S11P21S002-242

See merge request s11-s-project/S11P21S002!242
This commit is contained in:
정현조 2024-09-30 13:06:45 +09:00
commit c0e4d879fa
8 changed files with 92 additions and 43 deletions

View File

@ -16,7 +16,7 @@ export async function logout() {
export async function getAndSaveFcmToken() {
const fcmToken = await getFcmToken();
return api.post('/auth/fcm', { token: fcmToken }).then(({ data }) => ({ data, fcmToken }));
return api.post('/auth/fcm', { token: fcmToken }).then(() => fcmToken);
}
export async function createFcmNotification() {

View File

@ -1,4 +1,5 @@
import { cn } from '@/lib/utils';
import timeAgo from '@/utils/timeAgo';
import { AlarmResponse } from '@/types';
import { Mail, MailOpen, Trash2 } from 'lucide-react';
@ -11,18 +12,23 @@ export default function AlarmItem({
onRead: (alarmId: number) => void;
onDelete: (alarmId: number) => void;
}) {
const timeAgo = (date: string | Date) => {
const now = new Date();
const past = new Date(date);
const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000);
if (diffInSeconds < 60) return `${Math.max(diffInSeconds, 0)}초 전`;
const diffInMinutes = Math.floor(diffInSeconds / 60);
if (diffInMinutes < 60) return `${diffInMinutes}분 전`;
const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `${diffInHours}시간 전`;
const diffInDays = Math.floor(diffInHours / 24);
return `${diffInDays}일 전`;
const alarmTypeToMessage = (alarmType: string) => {
switch (alarmType) {
case 'PREDICT':
return '오토 레이블링이 완료되었습니다.';
case 'TRAIN':
return '학습이 완료되었습니다.';
case 'IMAGE':
return '이미지 업로드가 완료되었습니다.';
case 'COMMENT':
return '새로운 댓글이 추가되었습니다.';
case 'REVIEW_RESULT':
return '요청한 리뷰에 대한 결과가 등록되었습니다.';
case 'REVIEW_REQUEST':
return '새로운 리뷰 요청을 받았습니다.';
default:
return '새로운 알림입니다.';
}
};
const handleRead = () => {
@ -34,13 +40,15 @@ export default function AlarmItem({
};
return (
<div className="flex w-full items-center bg-white py-2 pr-[18px] duration-150 hover:bg-gray-200">
<div className={cn('mx-1.5 h-1.5 w-1.5 rounded-full', alarm.isRead ? 'bg-transparent' : 'bg-blue-500')}></div>
<div className="flex w-full items-center bg-white p-3 duration-150 hover:bg-gray-200">
<div className="flex flex-1 flex-col">
<p className={cn('body-small', alarm.isRead ? 'text-gray-400' : 'text-black')}>
[{alarm.id}] {alarm.type} .
</p>
<p className="caption text-gray-500">{timeAgo(alarm.createdAt)}</p>
<div className="flex items-center">
{!alarm.isRead && <div className="mr-1.5 h-1.5 w-1.5 rounded-full bg-orange-500"></div>}
<p className={cn('body-small', alarm.isRead ? 'text-gray-400' : 'text-black')}>
{alarmTypeToMessage(alarm.type)}
</p>
</div>
<p className={cn('caption', alarm.isRead ? 'text-gray-400' : 'text-gray-500')}>{timeAgo(alarm.createdAt)}</p>
</div>
{alarm.isRead ? (
<button

View File

@ -5,7 +5,8 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { onMessage } from 'firebase/messaging';
import { messaging } from '@/api/firebaseConfig';
import AlarmItem from './AlarmItem';
import useFcmTokenQuery from '@/queries/auth/useFcmTokenQuery';
import useGetAndSaveFcmTokenQuery from '@/queries/auth/useGetAndSaveFcmTokenQuery';
import useResetFcmTokenQuery from '@/queries/auth/useResetFcmTokenQuery';
import useGetAlarmListQuery from '@/queries/alarms/useGetAlarmListQuery';
import useResetAlarmListQuery from '@/queries/alarms/useResetAlarmListQuery';
import useCreateAlarmTestQuery from '@/queries/alarms/useCreateAlarmTestQuery';
@ -16,16 +17,13 @@ import useDeleteAllAlarmQuery from '@/queries/alarms/useDeleteAllAlarmQuery';
export default function AlarmPopover() {
const [unread, setUnread] = useState<boolean>(false);
const resetFcmToken = useResetFcmTokenQuery();
const resetAlarmList = useResetAlarmListQuery();
const createAlarmTest = useCreateAlarmTestQuery();
const readAlarm = useReadAlarmQuery();
const deleteAlarm = useDeleteAlarmQuery();
const deleteAllAlarm = useDeleteAllAlarmQuery();
const handleResetAlarmList = () => {
resetAlarmList.mutate();
};
const handleCreateAlarmTest = () => {
createAlarmTest.mutate();
};
@ -42,48 +40,66 @@ export default function AlarmPopover() {
deleteAllAlarm.mutate();
};
useFcmTokenQuery();
const { data: alarms } = useGetAlarmListQuery();
useGetAndSaveFcmTokenQuery();
const { data: alarmList } = useGetAlarmListQuery();
onMessage(messaging, (payload) => {
if (!payload.data) return;
console.log('new message arrived');
handleResetAlarmList();
resetAlarmList.mutate();
});
useEffect(() => {
const unreadCnt = alarms.filter((alarm) => !alarm.isRead).length;
const unreadCnt = alarmList.filter((alarm) => !alarm.isRead).length;
if (unreadCnt > 0) {
setUnread(true);
} else {
setUnread(false);
}
}, [alarms]);
}, [alarmList]);
useEffect(() => {
// 현재 창에 포커스 시 실행할 메서드
const handleFocus = () => {
resetFcmToken.mutate();
resetAlarmList.mutate();
};
// 현재 창에 포커스 해제 시 실행할 메서드
// const handleBlur = () => {};
// window에 focus와 blur 이벤트 리스너 등록
window.addEventListener('focus', handleFocus);
// window.addEventListener('blur', handleBlur);
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
return () => {
window.removeEventListener('focus', handleFocus);
// window.removeEventListener('blur', handleBlur);
};
}, [resetAlarmList, resetFcmToken]);
return (
<Popover>
<PopoverTrigger asChild>
<button
className="flex items-center justify-center p-2"
onClick={() => {}}
>
<button className="flex items-center justify-center p-2">
<Bell className="h-4 w-4 cursor-pointer text-black sm:h-5 sm:w-5" />
<div className={cn('mt-[14px] h-1.5 w-1.5 rounded-full', unread ? 'bg-blue-500' : 'bg-transparent')}></div>
<div className={cn('mt-[14px] h-1.5 w-1.5 rounded-full', unread ? 'bg-orange-500' : 'bg-transparent')}></div>
</button>
</PopoverTrigger>
<PopoverContent
className="w-80 overflow-hidden rounded-lg p-0"
className="w-[360px] overflow-hidden rounded-lg p-0"
align="end"
sideOffset={14}
alignOffset={0}
>
<div className="flex w-full items-center px-[18px] py-3">
<div className="flex w-full items-center p-3">
<h2 className="body-strong flex-1"></h2>
<button
className="body-small p-1 text-blue-500"
className="body-small p-1 text-gray-400"
onClick={handleCreateAlarmTest}
>
@ -112,13 +128,13 @@ export default function AlarmPopover() {
</div>
<hr />
{alarms.length === 0 ? (
<div className="flex w-full items-center px-[18px] py-3 duration-150">
{alarmList.length === 0 ? (
<div className="flex w-full items-center p-3 duration-150">
<p className="body-small text-gray-500"> .</p>
</div>
) : (
<div className="flex max-h-[500px] w-full flex-col items-center overflow-y-auto">
{alarms
{alarmList
.slice()
.reverse()
.map((alarm) => (

View File

@ -1,6 +1,6 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
export default function useUpdateAlarmQuery() {
export default function useResetAlarmListQuery() {
const queryClient = useQueryClient();
return useMutation({

View File

@ -1,7 +1,7 @@
import { getAndSaveFcmToken } from '@/api/authApi';
import { useSuspenseQuery } from '@tanstack/react-query';
export default function useFcmTokenQuery() {
export default function useGetAndSaveFcmTokenQuery() {
return useSuspenseQuery({
queryKey: ['fcmToken'],
queryFn: getAndSaveFcmToken,

View File

@ -0,0 +1,12 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
export default function useResetFcmTokenQuery() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fcmToken'] });
},
});
}

View File

@ -2,5 +2,5 @@ export interface AlarmResponse {
id: number;
isRead: boolean;
createdAt: string;
type: string;
type: 'PREDICT' | 'TRAIN' | 'IMAGE' | 'COMMENT' | 'REVIEW_RESULT' | 'REVIEW_REQUEST';
}

View File

@ -0,0 +1,13 @@
export default function timeAgo(date: string | Date) {
const now = new Date();
const past = new Date(date);
const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000);
if (diffInSeconds < 60) return `${Math.max(diffInSeconds, 0)}초 전`;
const diffInMinutes = Math.floor(diffInSeconds / 60);
if (diffInMinutes < 60) return `${diffInMinutes}분 전`;
const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) return `${diffInHours}시간 전`;
const diffInDays = Math.floor(diffInHours / 24);
return `${diffInDays}일 전`;
}