From f1d05c0e95c025a1cf9ac11a51e50e5daca914f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Fri, 27 Sep 2024 10:49:44 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20FCM=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=8B=9C=20FCM=20=ED=86=A0=ED=81=B0=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useOAuthCallback.ts | 7 +++++++ frontend/src/queries/auth/useGetFcmTokenQuery.ts | 9 +++++++++ frontend/src/queries/auth/useLogoutQuery.ts | 1 + frontend/src/queries/auth/useSaveFcmTokenQuery.ts | 8 ++++++++ frontend/src/stores/useAuthStore.ts | 6 +++++- 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 frontend/src/queries/auth/useGetFcmTokenQuery.ts create mode 100644 frontend/src/queries/auth/useSaveFcmTokenQuery.ts diff --git a/frontend/src/hooks/useOAuthCallback.ts b/frontend/src/hooks/useOAuthCallback.ts index 9281ea7..d88862c 100644 --- a/frontend/src/hooks/useOAuthCallback.ts +++ b/frontend/src/hooks/useOAuthCallback.ts @@ -1,11 +1,14 @@ import useAuthStore from '@/stores/useAuthStore'; import useProfileQuery from '@/queries/auth/useProfileQuery'; +import useGetFcmTokenQuery from '@/queries/auth/useGetFcmTokenQuery'; +import useSaveFcmTokenQuery from '@/queries/auth/useSaveFcmTokenQuery'; export default function useHandleOAuthCallback() { const queryParams = new URLSearchParams(window.location.search); const accessToken = queryParams.get('accessToken'); const setToken = useAuthStore((state) => state.setToken); const setProfile = useAuthStore((state) => state.setProfile); + const setFcmToken = useAuthStore((state) => state.setFcmToken); if (accessToken) { setToken(accessToken); @@ -16,4 +19,8 @@ export default function useHandleOAuthCallback() { if (profile) { setProfile(profile); } + + const { data: fcmToken } = useGetFcmTokenQuery(); // FCM 토큰을 가져온다. + setFcmToken(fcmToken ?? ''); // FCM 토큰을 authStore에 저장한다. + useSaveFcmTokenQuery().mutate(fcmToken ?? ''); // FCM 토큰을 서버에 전송한다. } diff --git a/frontend/src/queries/auth/useGetFcmTokenQuery.ts b/frontend/src/queries/auth/useGetFcmTokenQuery.ts new file mode 100644 index 0000000..2339233 --- /dev/null +++ b/frontend/src/queries/auth/useGetFcmTokenQuery.ts @@ -0,0 +1,9 @@ +import { getFcmToken } from '@/api/firebaseConfig'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useGetFcmTokenQuery() { + return useSuspenseQuery({ + queryKey: ['fcmToken'], + queryFn: getFcmToken, + }); +} diff --git a/frontend/src/queries/auth/useLogoutQuery.ts b/frontend/src/queries/auth/useLogoutQuery.ts index 2379128..4eac0ce 100644 --- a/frontend/src/queries/auth/useLogoutQuery.ts +++ b/frontend/src/queries/auth/useLogoutQuery.ts @@ -11,6 +11,7 @@ export default function useLogoutQuery() { onSuccess: () => { clearAuth(); queryClient.invalidateQueries({ queryKey: ['profile'] }); + queryClient.invalidateQueries({ queryKey: ['fcmToken'] }); }, }); } diff --git a/frontend/src/queries/auth/useSaveFcmTokenQuery.ts b/frontend/src/queries/auth/useSaveFcmTokenQuery.ts new file mode 100644 index 0000000..51df551 --- /dev/null +++ b/frontend/src/queries/auth/useSaveFcmTokenQuery.ts @@ -0,0 +1,8 @@ +import { saveFcmToken } from '@/api/authApi'; +import { useMutation } from '@tanstack/react-query'; + +export default function useSaveFcmTokenQuery() { + return useMutation({ + mutationFn: (fcmToken: string) => saveFcmToken(fcmToken), + }); +} diff --git a/frontend/src/stores/useAuthStore.ts b/frontend/src/stores/useAuthStore.ts index fa48c8a..5acb6ab 100644 --- a/frontend/src/stores/useAuthStore.ts +++ b/frontend/src/stores/useAuthStore.ts @@ -5,8 +5,10 @@ import { MemberResponse } from '@/types'; interface AuthState { accessToken: string; profile: MemberResponse | null; + fcmToken: string; setToken: (token: string) => void; setProfile: (profile: MemberResponse) => void; + setFcmToken: (fcmToken: string) => void; clearAuth: () => void; } @@ -15,9 +17,11 @@ const useAuthStore = create()( (set) => ({ accessToken: '', profile: null, + fcmToken: '', setToken: (token: string) => set({ accessToken: token }), setProfile: (profile: MemberResponse) => set({ profile }), - clearAuth: () => set({ accessToken: '', profile: null }), + setFcmToken: (fcmToken: string) => set({ fcmToken }), + clearAuth: () => set({ accessToken: '', profile: null, fcmToken: '' }), }), { name: 'auth-storage', From 91c9faa66905d35e715fda3b65f407a7ef451aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Fri, 27 Sep 2024 15:46:35 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Refactor:=20FCM=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20=EB=B6=88=EB=9F=AC=EC=99=80=EC=84=9C=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EC=97=90=20=EB=B0=94=EB=A1=9C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/authApi.ts | 6 ++- frontend/src/api/firebaseConfig.ts | 43 ++++++++++--------- frontend/src/hooks/useOAuthCallback.ts | 7 --- frontend/src/pages/FirebaseTest.tsx | 19 ++------ .../src/queries/auth/useGetFcmTokenQuery.ts | 9 ---- .../src/queries/auth/useSaveFcmTokenQuery.ts | 8 ---- frontend/src/stores/useAuthStore.ts | 6 +-- 7 files changed, 30 insertions(+), 68 deletions(-) delete mode 100644 frontend/src/queries/auth/useGetFcmTokenQuery.ts delete mode 100644 frontend/src/queries/auth/useSaveFcmTokenQuery.ts diff --git a/frontend/src/api/authApi.ts b/frontend/src/api/authApi.ts index 79f76e9..c6373da 100644 --- a/frontend/src/api/authApi.ts +++ b/frontend/src/api/authApi.ts @@ -1,5 +1,6 @@ import api from '@/api/axiosConfig'; import { MemberResponse, RefreshTokenResponse } from '@/types'; +import { getFcmToken } from './firebaseConfig'; export async function reissueToken() { return api.post('/auth/reissue', null, { withCredentials: true }).then(({ data }) => data); @@ -13,8 +14,9 @@ export async function logout() { return api.post('/auth/logout').then(({ data }) => data); } -export async function saveFcmToken(token: string) { - return api.post('/auth/fcm', { token }).then(({ data }) => data); +export async function saveFcmToken() { + const fcmToken = await getFcmToken(); + return api.post('/auth/fcm', { token: fcmToken }).then(({ data }) => ({ data, fcmToken })); } export async function createFcmNotification() { diff --git a/frontend/src/api/firebaseConfig.ts b/frontend/src/api/firebaseConfig.ts index 3e71c54..d0e7103 100644 --- a/frontend/src/api/firebaseConfig.ts +++ b/frontend/src/api/firebaseConfig.ts @@ -1,5 +1,5 @@ import { initializeApp } from 'firebase/app'; -import { getMessaging, getToken, onMessage } from 'firebase/messaging'; +import { getMessaging, onMessage } from 'firebase/messaging'; const firebaseConfig = { apiKey: String(import.meta.env.VITE_FIREBASE_API_KEY), @@ -22,30 +22,31 @@ const getFcmToken = async () => { return existingToken; } - try { - const permission = await Notification.requestPermission(); + // try { + // const permission = await Notification.requestPermission(); - if (permission === 'granted') { - console.log('알림 권한이 허용되었습니다.'); + // if (permission === 'granted') { + // console.log('알림 권한이 허용되었습니다.'); - console.log('FCM 토큰 발급 중...'); - const currentToken = await getToken(messaging, { - vapidKey: 'BApIruZrx83suCd09dnDCkFSP_Ts08q38trrIL6GHpChtbjQHTHk_38_JRyTiKLqciHxLQ8iXtie3lvgyb4Iphg', - }); - console.log('FCM 토큰 발급 성공'); + // console.log('FCM 토큰 발급 중...'); + // const currentToken = await getToken(messaging, { + // vapidKey: 'BApIruZrx83suCd09dnDCkFSP_Ts08q38trrIL6GHpChtbjQHTHk_x38_JRyTiKLqciHxLQ8iXtie3lvgyb4Iphg', + // }); + // console.log('FCM 토큰 발급 성공'); - if (currentToken) { - sessionStorage.setItem('fcmToken', currentToken); - return currentToken; - } + // if (currentToken) { + // sessionStorage.setItem('fcmToken', currentToken); + // return currentToken; + // } - console.warn('FCM 토큰을 가져올 수 없습니다.'); - } else { - console.log('알림 권한이 거부되었습니다.'); - } - } catch (error) { - console.error('FCM 토큰을 가져오는 중 오류가 발생했습니다. : ', error); - } + // console.warn('FCM 토큰을 가져올 수 없습니다.'); + // } else { + // console.log('알림 권한이 거부되었습니다.'); + // } + // } catch (error) { + // console.error('FCM 토큰을 가져오는 중 오류가 발생했습니다. : ', error); + // } + return null; }; const handleForegroundMessages = () => { diff --git a/frontend/src/hooks/useOAuthCallback.ts b/frontend/src/hooks/useOAuthCallback.ts index d88862c..9281ea7 100644 --- a/frontend/src/hooks/useOAuthCallback.ts +++ b/frontend/src/hooks/useOAuthCallback.ts @@ -1,14 +1,11 @@ import useAuthStore from '@/stores/useAuthStore'; import useProfileQuery from '@/queries/auth/useProfileQuery'; -import useGetFcmTokenQuery from '@/queries/auth/useGetFcmTokenQuery'; -import useSaveFcmTokenQuery from '@/queries/auth/useSaveFcmTokenQuery'; export default function useHandleOAuthCallback() { const queryParams = new URLSearchParams(window.location.search); const accessToken = queryParams.get('accessToken'); const setToken = useAuthStore((state) => state.setToken); const setProfile = useAuthStore((state) => state.setProfile); - const setFcmToken = useAuthStore((state) => state.setFcmToken); if (accessToken) { setToken(accessToken); @@ -19,8 +16,4 @@ export default function useHandleOAuthCallback() { if (profile) { setProfile(profile); } - - const { data: fcmToken } = useGetFcmTokenQuery(); // FCM 토큰을 가져온다. - setFcmToken(fcmToken ?? ''); // FCM 토큰을 authStore에 저장한다. - useSaveFcmTokenQuery().mutate(fcmToken ?? ''); // FCM 토큰을 서버에 전송한다. } diff --git a/frontend/src/pages/FirebaseTest.tsx b/frontend/src/pages/FirebaseTest.tsx index cdae563..678a258 100644 --- a/frontend/src/pages/FirebaseTest.tsx +++ b/frontend/src/pages/FirebaseTest.tsx @@ -1,36 +1,23 @@ import { createFcmNotification, saveFcmToken } from '@/api/authApi'; -import { getFcmToken, handleForegroundMessages } from '@/api/firebaseConfig'; +import { handleForegroundMessages } from '@/api/firebaseConfig'; import { Button } from '@/components/ui/button'; export default function FirebaseTest() { const handleSaveFcmToken = async () => { - const fcmToken = await getFcmToken(); - - if (fcmToken) { - await saveFcmToken(fcmToken); - return; - } - - console.log('FCM 토큰이 없습니다.'); + await saveFcmToken(); }; const handleCreateNotification = async () => { await createFcmNotification(); }; + handleSaveFcmToken(); handleForegroundMessages(); return (

hello, firebase!

- + + + + ); +} diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 48666ce..37797fc 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -1,10 +1,10 @@ import { cn } from '@/lib/utils'; -import { Bell } from 'lucide-react'; import { useLocation, Link } from 'react-router-dom'; import UserProfileModal from './UserProfileModal'; import WorkspaceNavigation from './WorkspaceNavigation'; import useAuthStore from '@/stores/useAuthStore'; import { Suspense } from 'react'; +import AlarmPopover from './AlarmPopover'; export interface HeaderProps extends React.HTMLAttributes {} @@ -40,7 +40,7 @@ export default function Header({ className, ...props }: HeaderProps) { {!isHomePage && profile && (
- +
)} diff --git a/frontend/src/pages/FirebaseTest.tsx b/frontend/src/pages/FirebaseTest.tsx deleted file mode 100644 index 678a258..0000000 --- a/frontend/src/pages/FirebaseTest.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { createFcmNotification, saveFcmToken } from '@/api/authApi'; -import { handleForegroundMessages } from '@/api/firebaseConfig'; -import { Button } from '@/components/ui/button'; - -export default function FirebaseTest() { - const handleSaveFcmToken = async () => { - await saveFcmToken(); - }; - - const handleCreateNotification = async () => { - await createFcmNotification(); - }; - - handleSaveFcmToken(); - handleForegroundMessages(); - - return ( -
-

hello, firebase!

-
- -
-
- ); -} diff --git a/frontend/src/queries/auth/useFcmTokenQuery.ts b/frontend/src/queries/auth/useFcmTokenQuery.ts new file mode 100644 index 0000000..9358bbd --- /dev/null +++ b/frontend/src/queries/auth/useFcmTokenQuery.ts @@ -0,0 +1,9 @@ +import { getAndSaveFcmToken } from '@/api/authApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useFcmTokenQuery() { + return useSuspenseQuery({ + queryKey: ['fcmToken'], + queryFn: getAndSaveFcmToken, + }); +} diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 066d6cd..02591de 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -20,7 +20,6 @@ import NotFound from '@/pages/NotFound'; import ReviewRequest from '@/pages/ReviewRequest'; import ModelIndex from '@/pages/ModelIndex'; import ModelDetail from '@/pages/ModelDetail'; -import FirebaseTest from '@/pages/FirebaseTest'; export const webPath = { home: () => '/', @@ -28,7 +27,6 @@ export const webPath = { workspace: () => '/workspace', admin: () => `/admin`, oauthCallback: () => '/redirect/oauth2', - firebaseTest: () => '/firebaseTest', }; const router = createBrowserRouter([ @@ -151,14 +149,6 @@ const router = createBrowserRouter([ ), }, - { - path: webPath.firebaseTest(), - element: ( -
}> - - - ), - }, ]); export default router; From 65037a4eff7da611171a04bd5612cd24a7d63828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Sun, 29 Sep 2024 20:20:00 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Feat:=20=EC=95=8C=EB=A6=BC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80=20-=20S11P21S0?= =?UTF-8?q?02-227?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/alarmApi.ts | 2 +- .../src/components/Header/AlarmPopover.tsx | 206 ++++++++++++++---- frontend/src/components/Header/index.tsx | 2 +- ...armQuery.ts => useCreateAlarmTestQuery.ts} | 6 +- .../queries/alarms/useResetAlarmListQuery.ts | 12 + 5 files changed, 179 insertions(+), 49 deletions(-) rename frontend/src/queries/alarms/{useCreateTestAlarmQuery.ts => useCreateAlarmTestQuery.ts} (63%) create mode 100644 frontend/src/queries/alarms/useResetAlarmListQuery.ts diff --git a/frontend/src/api/alarmApi.ts b/frontend/src/api/alarmApi.ts index de4d95a..6d9fc4c 100644 --- a/frontend/src/api/alarmApi.ts +++ b/frontend/src/api/alarmApi.ts @@ -5,7 +5,7 @@ export async function getAlarmList() { return api.get('/alarm').then(({ data }) => data); } -export async function createTestAlarm() { +export async function createAlarmTest() { return api.post('/alarm/test').then(({ data }) => data); } diff --git a/frontend/src/components/Header/AlarmPopover.tsx b/frontend/src/components/Header/AlarmPopover.tsx index c8d401d..5f241b8 100644 --- a/frontend/src/components/Header/AlarmPopover.tsx +++ b/frontend/src/components/Header/AlarmPopover.tsx @@ -1,68 +1,186 @@ -import { Bell } from 'lucide-react'; -import { Button } from '../ui/button'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; +import { useEffect, useState } from 'react'; +import { cn } from '@/lib/utils'; import { onMessage } from 'firebase/messaging'; import { messaging } from '@/api/firebaseConfig'; -import useGetAlarmListQuery from '@/queries/alarms/useGetAlarmListQuery'; import useFcmTokenQuery from '@/queries/auth/useFcmTokenQuery'; -import useCreateTestAlarmQuery from '@/queries/alarms/useCreateTestAlarmQuery'; -// import useDeleteAlarmQuery from '@/queries/alarms/useDeleteAlarmQuery'; +import useGetAlarmListQuery from '@/queries/alarms/useGetAlarmListQuery'; +import useResetAlarmListQuery from '@/queries/alarms/useResetAlarmListQuery'; +import useCreateAlarmTestQuery from '@/queries/alarms/useCreateAlarmTestQuery'; +import useReadAlarmQuery from '@/queries/alarms/useReadAlarmQuery'; +import useDeleteAlarmQuery from '@/queries/alarms/useDeleteAlarmQuery'; import useDeleteAllAlarmQuery from '@/queries/alarms/useDeleteAllAlarmQuery'; +import { Bell, Mail, MailOpen, Trash2 } from 'lucide-react'; +import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; export default function AlarmPopover() { - useFcmTokenQuery(); + const [unread, setUnread] = useState(false); - const { data: alarms } = useGetAlarmListQuery(); + const timeAgo = (date: string | Date) => { + const now = new Date(); + const past = new Date(date); + const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1000); - onMessage(messaging, (payload) => { - if (!payload.data) return; - - console.log(payload.data); - }); - - const createTestAlarm = useCreateTestAlarmQuery(); - // const deleteAlarm = useDeleteAlarmQuery(); - const deleteAllAlarm = useDeleteAllAlarmQuery(); - - const handleCreateTestAlarm = () => { - createTestAlarm.mutate(); + 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 handleDeleteAlarm = (alarmId: number) => { - // deleteAlarm.mutate(alarmId); - // }; + const resetAlarmList = useResetAlarmListQuery(); + const createAlarmTest = useCreateAlarmTestQuery(); + const readAlarm = useReadAlarmQuery(); + const deleteAlarm = useDeleteAlarmQuery(); + const deleteAllAlarm = useDeleteAllAlarmQuery(); + + const handleResetAlarmList = () => { + resetAlarmList.mutate(); + }; + + const handleCreateAlarmTest = () => { + createAlarmTest.mutate(); + }; + + const handleReadAlarm = (alarmId: number) => { + readAlarm.mutate(alarmId); + }; + + const handleDeleteAlarm = (alarmId: number) => { + deleteAlarm.mutate(alarmId); + }; const handleDeleteAllAlarm = () => { deleteAllAlarm.mutate(); }; + useFcmTokenQuery(); + const { data: alarms } = useGetAlarmListQuery(); + + onMessage(messaging, (payload) => { + if (!payload.data) return; + + console.log('new message arrived'); + handleResetAlarmList(); + }); + + useEffect(() => { + const unreadCnt = alarms.filter((alarm) => !alarm.isRead).length; + + if (unreadCnt > 0) { + setUnread(true); + } else { + setUnread(false); + } + }, [alarms]); + return ( -
+
+
+
- - {alarms.map((alarm, index) => ( -
- {alarm.id} {alarm.type} + + +
+

알림

+ + {unread ? ( + + ) : ( + + )} + +
+
+ + {alarms.length == 0 && ( +
+

알림이 없습니다.

- ))} - - + )} + + {alarms + .slice() + .reverse() + .map((alarm) => ( +
+
+
+

+ [{alarm.id}] {alarm.type} 알림입니다. +

+

{timeAgo(alarm.createdAt)}

+
+ {alarm.isRead ? ( + + ) : ( + + )} + +
+ ))}
); diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 37797fc..9e8a78f 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -39,7 +39,7 @@ export default function Header({ className, ...props }: HeaderProps) {
{!isHomePage && profile && ( -
+
diff --git a/frontend/src/queries/alarms/useCreateTestAlarmQuery.ts b/frontend/src/queries/alarms/useCreateAlarmTestQuery.ts similarity index 63% rename from frontend/src/queries/alarms/useCreateTestAlarmQuery.ts rename to frontend/src/queries/alarms/useCreateAlarmTestQuery.ts index afcf652..84c6388 100644 --- a/frontend/src/queries/alarms/useCreateTestAlarmQuery.ts +++ b/frontend/src/queries/alarms/useCreateAlarmTestQuery.ts @@ -1,11 +1,11 @@ -import { createTestAlarm } from '@/api/alarmApi'; +import { createAlarmTest } from '@/api/alarmApi'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -export default function useCreateTestAlarmQuery() { +export default function useCreateAlarmTestQuery() { const queryClient = useQueryClient(); return useMutation({ - mutationFn: createTestAlarm, + mutationFn: createAlarmTest, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['alarmList'] }); }, diff --git a/frontend/src/queries/alarms/useResetAlarmListQuery.ts b/frontend/src/queries/alarms/useResetAlarmListQuery.ts new file mode 100644 index 0000000..75fd6d4 --- /dev/null +++ b/frontend/src/queries/alarms/useResetAlarmListQuery.ts @@ -0,0 +1,12 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +export default function useUpdateAlarmQuery() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async () => {}, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['alarmList'] }); + }, + }); +} From 08eb021e567d3f91e9c5392b19b105a784407a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Sun, 29 Sep 2024 21:14:47 +0900 Subject: [PATCH 9/9] =?UTF-8?q?Refactor:=20=EC=95=8C=EB=A6=BC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Header/AlarmItem.tsx | 74 +++++++++++++++ .../src/components/Header/AlarmPopover.tsx | 90 +++++-------------- 2 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 frontend/src/components/Header/AlarmItem.tsx diff --git a/frontend/src/components/Header/AlarmItem.tsx b/frontend/src/components/Header/AlarmItem.tsx new file mode 100644 index 0000000..49a565d --- /dev/null +++ b/frontend/src/components/Header/AlarmItem.tsx @@ -0,0 +1,74 @@ +import { cn } from '@/lib/utils'; +import { AlarmResponse } from '@/types'; +import { Mail, MailOpen, Trash2 } from 'lucide-react'; + +export default function AlarmItem({ + alarm, + onRead, + onDelete, +}: { + alarm: AlarmResponse; + 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 handleRead = () => { + onRead(alarm.id); + }; + + const handleDelete = () => { + onDelete(alarm.id); + }; + + return ( +
+
+
+

+ [{alarm.id}] {alarm.type} 알림입니다. +

+

{timeAgo(alarm.createdAt)}

+
+ {alarm.isRead ? ( + + ) : ( + + )} + +
+ ); +} diff --git a/frontend/src/components/Header/AlarmPopover.tsx b/frontend/src/components/Header/AlarmPopover.tsx index 5f241b8..66108a2 100644 --- a/frontend/src/components/Header/AlarmPopover.tsx +++ b/frontend/src/components/Header/AlarmPopover.tsx @@ -1,7 +1,10 @@ import { useEffect, useState } from 'react'; import { cn } from '@/lib/utils'; +import { Bell } from 'lucide-react'; +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 useGetAlarmListQuery from '@/queries/alarms/useGetAlarmListQuery'; import useResetAlarmListQuery from '@/queries/alarms/useResetAlarmListQuery'; @@ -9,26 +12,10 @@ import useCreateAlarmTestQuery from '@/queries/alarms/useCreateAlarmTestQuery'; import useReadAlarmQuery from '@/queries/alarms/useReadAlarmQuery'; import useDeleteAlarmQuery from '@/queries/alarms/useDeleteAlarmQuery'; import useDeleteAllAlarmQuery from '@/queries/alarms/useDeleteAllAlarmQuery'; -import { Bell, Mail, MailOpen, Trash2 } from 'lucide-react'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; export default function AlarmPopover() { const [unread, setUnread] = useState(false); - 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 resetAlarmList = useResetAlarmListQuery(); const createAlarmTest = useCreateAlarmTestQuery(); const readAlarm = useReadAlarmQuery(); @@ -101,7 +88,7 @@ export default function AlarmPopover() { > 테스트 - {unread ? ( + {/* {unread ? ( - )} + )} */} - ) : ( - - )} - -
- ))} + ))} +
+ )} );