From a9e388f6233084a48c4ac8ebf64c24f394c39b20 Mon Sep 17 00:00:00 2001 From: kanguk Date: Fri, 15 Nov 2024 02:33:53 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=88=84=EB=A5=B4?= =?UTF-8?q?=EB=A9=B4=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/firebase-messaging-sw.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js index 6bdb526..3995552 100644 --- a/public/firebase-messaging-sw.js +++ b/public/firebase-messaging-sw.js @@ -25,7 +25,19 @@ messaging.onBackgroundMessage((payload) => { const notificationOptions = { body: payload.notification.body, icon: "/icon.png", // 아이콘 이미지 경로 + data: { click_action: payload.data.click_action } // 클릭 시 이동할 URL을 data에 추가 }; self.registration.showNotification(notificationTitle, notificationOptions); }); + +// 알림 클릭 이벤트 리스너 추가 +self.addEventListener("notificationclick", (event) => { + event.notification.close(); // 알림 닫기 + + // 알림에 설정된 URL로 이동 + const targetUrl = event.notification.data.click_action || "https://splanet.co.kr/"; + event.waitUntil( + clients.openWindow(targetUrl) + ); +}); From a7490fba9055d24ba109bd9eb962ebc42d98e1a0 Mon Sep 17 00:00:00 2001 From: kanguk Date: Fri, 15 Nov 2024 03:04:41 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/firebase-messaging-sw.js | 2 +- src/assets/icon.png | Bin 0 -> 13278 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/assets/icon.png diff --git a/public/firebase-messaging-sw.js b/public/firebase-messaging-sw.js index 3995552..d6f8184 100644 --- a/public/firebase-messaging-sw.js +++ b/public/firebase-messaging-sw.js @@ -24,7 +24,7 @@ messaging.onBackgroundMessage((payload) => { const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, - icon: "/icon.png", // 아이콘 이미지 경로 + icon: "./src/assets/icon.png", // 아이콘 이미지 경로 data: { click_action: payload.data.click_action } // 클릭 시 이동할 URL을 data에 추가 }; diff --git a/src/assets/icon.png b/src/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bdc825f9ec6ed746b15fb9c62b7b52008e0b8654 GIT binary patch literal 13278 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 cV3Sb(KU$m&r)0o?y#N3J07*qoM6N<$f`E Date: Fri, 15 Nov 2024 03:26:59 +0900 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=B0=9C=EA=B8=89=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useNotificationSetup.ts | 36 ++++++++++ src/pages/Main/MainPage.tsx | 110 ++++++------------------------ 2 files changed, 58 insertions(+), 88 deletions(-) create mode 100644 src/hooks/useNotificationSetup.ts diff --git a/src/hooks/useNotificationSetup.ts b/src/hooks/useNotificationSetup.ts new file mode 100644 index 0000000..161e931 --- /dev/null +++ b/src/hooks/useNotificationSetup.ts @@ -0,0 +1,36 @@ +import { useEffect } from "react"; +import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig"; +import { apiClient } from "@/api/instance"; + +// 전역 상태 +let isInitialized = false; +let fcmTokenRegistered = false; + +const useNotificationSetup = () => { + useEffect(() => { + const initializeFCM = async () => { + if (!isInitialized) { + console.log("Initializing FCM..."); + isInitialized = true; + + if (!fcmTokenRegistered) { + const permission = await Notification.requestPermission(); + if (permission === "granted") { + const fcmToken = await requestForToken(); + if (fcmToken) { + await apiClient.post("/api/fcm/register", { token: fcmToken }); + fcmTokenRegistered = true; + } + } + } + + setupOnMessageListener(); + console.log("FCM initialization complete"); + } + }; + + initializeFCM(); + }, []); +}; + +export default useNotificationSetup; diff --git a/src/pages/Main/MainPage.tsx b/src/pages/Main/MainPage.tsx index 33225c7..7a706cc 100644 --- a/src/pages/Main/MainPage.tsx +++ b/src/pages/Main/MainPage.tsx @@ -11,9 +11,9 @@ import useDeletePlan from "@/api/hooks/useDeletePlan"; import Button from "@/components/common/Button/Button"; import Modal from "@/components/common/Modal/Modal"; import ReactDatePicker from "@/components/features/DatePicker/DatePicker"; -import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig"; import { apiClient } from "@/api/instance"; import useUserData from "@/api/hooks/useUserData"; +import useNotificationSetup from "@/hooks/useNotificationSetup"; const PageContainer = styled.div` background-color: #ffffff; @@ -83,6 +83,8 @@ const Spinner = styled.div` `; export default function MainPage() { + useNotificationSetup(); + const location = useLocation(); const navigate = useNavigate(); const { data: fetchedPlans, isLoading, error, refetch } = useGetPlans(); @@ -99,87 +101,9 @@ export default function MainPage() { const { mutateAsync: createPlan } = useCreatePlan(); const { mutateAsync: deletePlan } = useDeletePlan(); const { userData } = useUserData(); - const isTokenRegistered = useRef(false); - const hasMounted = useRef(false); const savePlanMutation = useCreatePlan(); const isPlanSaved = useRef(false); - // FCM 토큰 등록 함수 - const registerFcmToken = async () => { - // 이미 토큰이 등록되어 있다면 종료 - if (isTokenRegistered.current) { - console.log("이미 FCM 토큰이 등록되어 있습니다."); - return; - } - - // localStorage에서 토큰 확인 - const storedToken = localStorage.getItem("fcmToken"); - if (storedToken) { - console.log( - "저장된 FCM 토큰을 사용합니다:", - `${storedToken.slice(0, 10)}...`, - ); - isTokenRegistered.current = true; - return; - } - - try { - console.log("FCM 토큰 등록 시작..."); - const permission = await Notification.requestPermission(); - console.log("알림 권한 상태:", permission); - - if (permission === "granted") { - const fcmToken = await requestForToken(); - if (fcmToken) { - console.log("새로운 FCM 토큰 발급됨:", `${fcmToken.slice(0, 10)}...`); - await apiClient.post("/api/fcm/register", { token: fcmToken }); - localStorage.setItem("fcmToken", fcmToken); - isTokenRegistered.current = true; - console.log("FCM 토큰 등록 완료"); - } else { - console.warn("FCM 토큰이 null입니다."); - } - } else { - console.warn("알림 권한이 거부되었습니다."); - } - } catch (err) { - console.error("FCM 토큰 등록 중 오류 발생:", err); - } - }; - // Notification functionality (기존 코드 유지) - useEffect(() => { - const registerFcmToken = async () => { - const permission = await Notification.requestPermission(); - if (permission === "granted") { - try { - const fcmToken = await requestForToken(); - if (fcmToken) { - await apiClient.post("/api/fcm/register", { token: fcmToken }); - console.log("FCM 토큰이 성공적으로 등록되었습니다."); - } - } catch (err) { - console.error("FCM 토큰 등록 중 오류 발생:", err); - } - } else { - console.log("알림 권한이 거부되었습니다."); - } - }; - - registerFcmToken(); - setupOnMessageListener(); // Set up the listener for foreground messages - }, []); - // 앱 초기 마운트시에만 FCM 토큰 등록 및 리스너 설정 - useEffect(() => { - if (!hasMounted.current) { - console.log("FCM 초기화 시작..."); - registerFcmToken().then(() => { - console.log("FCM 초기화 완료"); - setupOnMessageListener(); - }); - hasMounted.current = true; - } - }, []); - // 플랜 데이터 초기화 useEffect(() => { if (fetchedPlans) { @@ -282,8 +206,8 @@ export default function MainPage() { accessibility: true, isCompleted: false, }); - } catch (error) { - alert(`추가 중 오류 발생: ${error}`); + } catch (err) { + alert(`추가 중 오류 발생: ${err}`); } }; @@ -296,8 +220,8 @@ export default function MainPage() { setModifiedPlans((prevPlans) => prevPlans.filter((plan) => plan.id !== planId), ); - } catch (error) { - alert(`삭제 중 오류 발생: ${error}`); + } catch (err) { + alert(`삭제 중 오류 발생: ${err}`); } } }; @@ -324,8 +248,8 @@ export default function MainPage() { // 상태 업데이트 setModifiedPlans(updatedPlans); - // 변경된 플랜들에 대해 서버에 업데이트 - for (const plan of changedPlans) { + // Promise.all을 사용하여 모든 플랜을 병렬로 업데이트 + const updatePlans = changedPlans.map(async (plan) => { try { await apiClient.put(`/api/plans/${plan.id}`, { title: plan.title, @@ -336,13 +260,23 @@ export default function MainPage() { isCompleted: plan.isCompleted ?? false, }); console.log(`플랜 ID ${plan.id}이 성공적으로 업데이트되었습니다.`); - } catch (error: unknown) { - if (error instanceof Error) { - alert(`플랜 업데이트 중 오류 발생: ${error.message}`); + } catch (err) { + if (err instanceof Error) { + alert(`플랜 업데이트 중 오류 발생: ${err.message}`); } else { alert("플랜 업데이트 중 알 수 없는 오류가 발생했습니다."); } + // 필요시 개별 에러를 처리하거나 다시 던질 수 있습니다. + throw err; } + }); + + try { + await Promise.all(updatePlans); + console.log("모든 플랜이 성공적으로 업데이트되었습니다."); + } catch (err) { + console.error("일부 플랜 업데이트에 실패했습니다.", error); + // 추가적인 에러 핸들링 로직을 여기에 작성할 수 있습니다. } }, [modifiedPlans], From b11297f2880df30266ae1306415595e0260ab2a2 Mon Sep 17 00:00:00 2001 From: Kuheewon <119719994+HeHelee@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:36:48 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor:=20local=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useNotificationSetup.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/hooks/useNotificationSetup.ts b/src/hooks/useNotificationSetup.ts index 161e931..1232564 100644 --- a/src/hooks/useNotificationSetup.ts +++ b/src/hooks/useNotificationSetup.ts @@ -6,6 +6,8 @@ import { apiClient } from "@/api/instance"; let isInitialized = false; let fcmTokenRegistered = false; +const FCM_TOKEN_KEY = "fcm_token"; // 로컬 스토리지 키 + const useNotificationSetup = () => { useEffect(() => { const initializeFCM = async () => { @@ -18,8 +20,15 @@ const useNotificationSetup = () => { if (permission === "granted") { const fcmToken = await requestForToken(); if (fcmToken) { - await apiClient.post("/api/fcm/register", { token: fcmToken }); - fcmTokenRegistered = true; + try { + await apiClient.post("/api/fcm/register", { token: fcmToken }); + // 토큰을 로컬 스토리지에 저장 + localStorage.setItem(FCM_TOKEN_KEY, fcmToken); + fcmTokenRegistered = true; + console.log("FCM token saved to localStorage:", fcmToken); + } catch (error) { + console.error("Failed to register FCM token:", error); + } } } } @@ -31,6 +40,15 @@ const useNotificationSetup = () => { initializeFCM(); }, []); + + // 토큰을 가져오는 유틸리티 함수 추가 + const getFCMToken = () => { + return localStorage.getItem(FCM_TOKEN_KEY); + }; + + return { + getFCMToken, + }; }; export default useNotificationSetup; From e25ce5841878b524c3fa02da73bb6cc71e0440a3 Mon Sep 17 00:00:00 2001 From: Kuheewon <119719994+HeHelee@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:37:46 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feate:=20enalbled=20=ED=9B=85=20=EB=B0=94?= =?UTF-8?q?=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/hooks/useFcmUpdate.ts | 101 ++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 30 deletions(-) diff --git a/src/api/hooks/useFcmUpdate.ts b/src/api/hooks/useFcmUpdate.ts index b4643b6..0e804d9 100644 --- a/src/api/hooks/useFcmUpdate.ts +++ b/src/api/hooks/useFcmUpdate.ts @@ -1,33 +1,74 @@ +import { useEffect, useState } from "react"; import { useMutation } from "@tanstack/react-query"; -import { apiClient } from "../instance"; - -interface FcmUpdateRequest { - token: string; - isNotificatioinEnabled?: boolean; - notificationOffset?: number; -} - -interface FcmUpdateResponse { - message: string; -} - -const useFcmUpdate = () => { - return useMutation({ - mutationFn: async (data: FcmUpdateRequest) => { - const response = await apiClient.put( - "/api/fcm/update", - { - token: data.token, - isNotificationEnabled: data.isNotificatioinEnabled ?? true, - notificationOffset: data.notificationOffset ?? 0, - }, - ); - return response.data; - }, - onError: (error) => { - console.error("FCM 토큰 업데이트 실패:", error); - }, - }); +import { AxiosResponse } from "axios"; +import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig"; +import { apiClient } from "@/api/instance"; + +const FCM_TOKEN_KEY = "fcm_token"; + +// FCM 토큰을 서버에 등록하는 함수 +const registerFcmToken = async ( + token: string, +): Promise> => { + return apiClient.post("/api/fcm/register", { token }); +}; + +const useNotificationSetup = () => { + const [isInitialized, setIsInitialized] = useState(false); + const [error, setError] = useState(null); + + // FCM 토큰 등록에 대한 Mutation 설정 + const { mutateAsync, isError } = useMutation< + AxiosResponse, + Error, + string + >(registerFcmToken); + + useEffect(() => { + const initializeFCM = async () => { + try { + const savedToken = localStorage.getItem(FCM_TOKEN_KEY); + + if (!savedToken) { + console.log("Requesting FCM permission..."); + const permission = await Notification.requestPermission(); + + if (permission === "granted") { + const fcmToken = await requestForToken(); + + if (fcmToken) { + await mutateAsync(fcmToken); // 서버에 토큰 등록 + localStorage.setItem(FCM_TOKEN_KEY, fcmToken); // 토큰을 로컬에 저장 + console.log("FCM token registered successfully"); + } else { + throw new Error("Failed to get FCM token"); + } + } else { + throw new Error("Notification permission denied"); + } + } + + setupOnMessageListener(); // 메시지 리스너 설정 + setIsInitialized(true); + console.log("FCM initialization complete"); + } catch (err) { + console.error("FCM initialization failed:", err); + setError( + err instanceof Error ? err : new Error("Unknown error occurred"), + ); + } + }; + + if (!isInitialized) { + initializeFCM(); + } + + return () => { + // 필요한 경우 cleanup 로직 추가 + }; + }, [isInitialized, mutateAsync]); + + return { isInitialized, error, mutateAsync, isError }; // mutateAsync를 반환 }; -export default useFcmUpdate; +export default useNotificationSetup; From b6142631ef592f4861aabff05cdcd8f1a5b47122 Mon Sep 17 00:00:00 2001 From: Kuheewon <119719994+HeHelee@users.noreply.github.com> Date: Fri, 15 Nov 2024 08:52:10 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=83=9D=EC=84=B1=EB=90=98=EB=8A=94=20=EA=B1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/hooks/useFcmOffsetUpdate.ts | 29 +++++++++++++++++ src/hooks/useNotificationSetup.ts | 50 +++++++++++++++++------------ 2 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 src/api/hooks/useFcmOffsetUpdate.ts diff --git a/src/api/hooks/useFcmOffsetUpdate.ts b/src/api/hooks/useFcmOffsetUpdate.ts new file mode 100644 index 0000000..7cce10e --- /dev/null +++ b/src/api/hooks/useFcmOffsetUpdate.ts @@ -0,0 +1,29 @@ +import { useMutation } from "@tanstack/react-query"; +import { apiClient } from "../instance"; + +interface FcmOffsetUpdateRequest { + token: string; + notificationOffset: number; +} + +type FcmOffsetUpdateResponse = string; + +const useFcmOffsetUpdate = () => { + return useMutation({ + mutationFn: async (data: FcmOffsetUpdateRequest) => { + const response = await apiClient.put( + "/api/fcm/update/notification-offset", + { + token: data.token, + notificationOffset: data.notificationOffset, + }, + ); + return response.data; + }, + onError: (error) => { + console.error("FCM 알림 오프셋 업데이트 실패:", error); + }, + }); +}; + +export default useFcmOffsetUpdate; diff --git a/src/hooks/useNotificationSetup.ts b/src/hooks/useNotificationSetup.ts index 1232564..fb75bb7 100644 --- a/src/hooks/useNotificationSetup.ts +++ b/src/hooks/useNotificationSetup.ts @@ -1,47 +1,57 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig"; import { apiClient } from "@/api/instance"; -// 전역 상태 -let isInitialized = false; -let fcmTokenRegistered = false; +const FCM_TOKEN_KEY = "fcm_token"; -const FCM_TOKEN_KEY = "fcm_token"; // 로컬 스토리지 키 +// 모듈 레벨에서 전역 플래그 선언 +let isTokenRequestedGlobal = false; const useNotificationSetup = () => { + const tokenRequestedRef = useRef(false); + useEffect(() => { const initializeFCM = async () => { - if (!isInitialized) { - console.log("Initializing FCM..."); - isInitialized = true; + // 이미 토큰 요청이 진행 중이거나 완료된 경우 중복 실행 방지 + if (isTokenRequestedGlobal || tokenRequestedRef.current) { + return; + } + + try { + isTokenRequestedGlobal = true; // 전역 플래그 설정 + tokenRequestedRef.current = true; - if (!fcmTokenRegistered) { + const existingToken = localStorage.getItem(FCM_TOKEN_KEY); + if (!existingToken) { const permission = await Notification.requestPermission(); if (permission === "granted") { const fcmToken = await requestForToken(); if (fcmToken) { - try { - await apiClient.post("/api/fcm/register", { token: fcmToken }); - // 토큰을 로컬 스토리지에 저장 - localStorage.setItem(FCM_TOKEN_KEY, fcmToken); - fcmTokenRegistered = true; - console.log("FCM token saved to localStorage:", fcmToken); - } catch (error) { - console.error("Failed to register FCM token:", error); - } + await apiClient.post("/api/fcm/register", { token: fcmToken }); + localStorage.setItem(FCM_TOKEN_KEY, fcmToken); + console.log("New FCM token registered and saved:", fcmToken); } } } + // 메시지 리스너는 한 번만 설정 setupOnMessageListener(); - console.log("FCM initialization complete"); + } catch (error) { + console.error("Failed to initialize FCM:", error); + // 에러 발생 시 다음 시도를 위해 플래그 초기화 + isTokenRequestedGlobal = false; + tokenRequestedRef.current = false; } }; initializeFCM(); + + // 컴포넌트 언마운트 시 cleanup + return () => { + tokenRequestedRef.current = false; + }; }, []); - // 토큰을 가져오는 유틸리티 함수 추가 const getFCMToken = () => { return localStorage.getItem(FCM_TOKEN_KEY); }; From 06e86ce4bd8f5991ec73dcec0971ed3b3151637e Mon Sep 17 00:00:00 2001 From: Kuheewon <119719994+HeHelee@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:15:11 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20=EC=95=8C=EB=A6=BC=20=EB=B9=84?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/hooks/useFcmUpdate.ts | 97 +++++++--------------- src/pages/Mypage/Mypage.tsx | 146 ++++++++++++++++++---------------- 2 files changed, 106 insertions(+), 137 deletions(-) diff --git a/src/api/hooks/useFcmUpdate.ts b/src/api/hooks/useFcmUpdate.ts index 0e804d9..b856572 100644 --- a/src/api/hooks/useFcmUpdate.ts +++ b/src/api/hooks/useFcmUpdate.ts @@ -1,74 +1,35 @@ -import { useEffect, useState } from "react"; import { useMutation } from "@tanstack/react-query"; -import { AxiosResponse } from "axios"; -import { requestForToken, setupOnMessageListener } from "@/api/firebaseConfig"; import { apiClient } from "@/api/instance"; -const FCM_TOKEN_KEY = "fcm_token"; - -// FCM 토큰을 서버에 등록하는 함수 -const registerFcmToken = async ( - token: string, -): Promise> => { - return apiClient.post("/api/fcm/register", { token }); -}; - -const useNotificationSetup = () => { - const [isInitialized, setIsInitialized] = useState(false); - const [error, setError] = useState(null); - - // FCM 토큰 등록에 대한 Mutation 설정 - const { mutateAsync, isError } = useMutation< - AxiosResponse, - Error, - string - >(registerFcmToken); - - useEffect(() => { - const initializeFCM = async () => { - try { - const savedToken = localStorage.getItem(FCM_TOKEN_KEY); - - if (!savedToken) { - console.log("Requesting FCM permission..."); - const permission = await Notification.requestPermission(); - - if (permission === "granted") { - const fcmToken = await requestForToken(); - - if (fcmToken) { - await mutateAsync(fcmToken); // 서버에 토큰 등록 - localStorage.setItem(FCM_TOKEN_KEY, fcmToken); // 토큰을 로컬에 저장 - console.log("FCM token registered successfully"); - } else { - throw new Error("Failed to get FCM token"); - } - } else { - throw new Error("Notification permission denied"); - } - } - - setupOnMessageListener(); // 메시지 리스너 설정 - setIsInitialized(true); - console.log("FCM initialization complete"); - } catch (err) { - console.error("FCM initialization failed:", err); - setError( - err instanceof Error ? err : new Error("Unknown error occurred"), - ); +interface UpdateNotificationEnabledRequest { + token: string; + isNotificationEnabled: boolean; +} + +export const useUpdateNotificationEnabled = () => { + return useMutation({ + mutationFn: async ({ + token, + isNotificationEnabled, + }: UpdateNotificationEnabledRequest) => { + const response = await apiClient.put( + `/api/fcm/update/notification-enabled?token=${token}&isNotificationEnabled=${isNotificationEnabled}`, + {}, // 빈 body + { + withCredentials: true, + }, + ); + + return response.data; + }, + onError: (error: any) => { + if (error.response?.status === 401) { + console.error("인증 정보가 없거나 만료되었습니다."); + } else { + console.error("알림 설정 업데이트 실패:", error); } - }; - - if (!isInitialized) { - initializeFCM(); - } - - return () => { - // 필요한 경우 cleanup 로직 추가 - }; - }, [isInitialized, mutateAsync]); - - return { isInitialized, error, mutateAsync, isError }; // mutateAsync를 반환 + }, + }); }; -export default useNotificationSetup; +export default useUpdateNotificationEnabled; diff --git a/src/pages/Mypage/Mypage.tsx b/src/pages/Mypage/Mypage.tsx index 0f453ea..b175615 100644 --- a/src/pages/Mypage/Mypage.tsx +++ b/src/pages/Mypage/Mypage.tsx @@ -1,4 +1,3 @@ -// src/pages/MyPage.tsx import { useState, useEffect } from "react"; import styled from "@emotion/styled"; import { motion } from "framer-motion"; @@ -12,7 +11,7 @@ import useUserData from "@/api/hooks/useUserData"; import useAuth from "@/hooks/useAuth"; import RouterPath from "@/router/RouterPath"; import breakpoints from "@/variants/breakpoints"; -import useFcmUpdate from "@/api/hooks/useFcmUpdate"; +import useNotificationSetup from "@/api/hooks/useFcmUpdate"; // useNotificationSetup 훅을 사용 import { requestForToken } from "@/api/firebaseConfig"; const PageWrapper = styled.div` @@ -28,7 +27,7 @@ const PageWrapper = styled.div` const ContentWrapper = styled.main` flex-grow: 1; - padding: 32px; /* p-8 */ + padding: 32px; overflow: auto; ${breakpoints.mobile} { @@ -37,10 +36,10 @@ const ContentWrapper = styled.main` `; const Heading = styled.h1` - font-size: 24px; /* text-3xl */ - font-weight: 600; /* font-semibold */ - margin-bottom: 24px; /* mb-6 */ - color: #2d3748; /* text-gray-800 */ + font-size: 24px; + font-weight: 600; + margin-bottom: 24px; + color: #2d3748; ${breakpoints.mobile} { font-size: 20px; @@ -51,7 +50,7 @@ const Heading = styled.h1` const GridLayout = styled.div` display: grid; grid-template-columns: 1fr; - gap: 24px; /* gap-6 */ + gap: 24px; @media (min-width: 768px) { grid-template-columns: 1fr 1fr; @@ -59,14 +58,15 @@ const GridLayout = styled.div` `; const Card = styled(motion.div)` - background-color: #ffffff; /* bg-white */ - border-radius: 8px; /* rounded-lg */ - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* shadow-md */ - padding: 24px; /* p-6 */ - transition: box-shadow 0.2s; /* transition-shadow duration-200 */ + background-color: #ffffff; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 24px; + transition: box-shadow 0.2s; margin-bottom: 18px; + &:hover { - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); /* hover:shadow-lg */ + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); } ${breakpoints.mobile} { @@ -79,14 +79,15 @@ const ProfileCard = styled(motion.div)` display: flex; justify-content: space-between; align-items: center; - background-color: #ffffff; /* bg-white */ - border-radius: 8px; /* rounded-lg */ - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* shadow-md */ - padding: 24px; /* p-6 */ - transition: box-shadow 0.2s; /* transition-shadow duration-200 */ + background-color: #ffffff; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + padding: 24px; + transition: box-shadow 0.2s; margin-bottom: 18px; + &:hover { - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); /* hover:shadow-lg */ + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.15); } ${breakpoints.mobile} { @@ -100,7 +101,7 @@ const ProfileCard = styled(motion.div)` const CardHeader = styled.div` display: flex; align-items: center; - margin-bottom: 16px; /* mb-4 */ + margin-bottom: 16px; ${breakpoints.mobile} { margin-bottom: 8px; @@ -109,10 +110,10 @@ const CardHeader = styled.div` const CardTitle = styled.h3` margin: 0; - margin-left: 8px; /* ml-2 */ - font-size: 18px; /* text-xl */ - font-weight: 600; /* font-semibold */ - color: #4a5568; /* text-gray-700 */ + margin-left: 8px; + font-size: 18px; + font-weight: 600; + color: #4a5568; ${breakpoints.mobile} { font-size: 16px; @@ -121,7 +122,7 @@ const CardTitle = styled.h3` const CardContent = styled.div` font-size: 14px; - color: #4a5568; /* text-gray-700 */ + color: #4a5568; ${breakpoints.mobile} { font-size: 13px; @@ -131,7 +132,7 @@ const CardContent = styled.div` const DeleteButtonWrapper = styled.div` display: flex; justify-content: flex-end; - margin-top: 24px; /* mt-6 */ + margin-top: 24px; gap: 10px; ${breakpoints.mobile} { @@ -140,60 +141,75 @@ const DeleteButtonWrapper = styled.div` } `; +const FCM_TOKEN_KEY = "fcm_token"; + export default function MyPage() { const [isNotificationEnabled, setNotificationEnabled] = useState(false); const { userData, handleDeleteAccount, handleSubscription } = useUserData(); const { setAuthState } = useAuth(); const navigate = useNavigate(); - const { mutateAsync: updateFcm } = useFcmUpdate(); + const fcmUpdateMutation = useNotificationSetup(); // 초기 알림 상태 설정 useEffect(() => { - const storedToken = localStorage.getItem("fcmToken"); - setNotificationEnabled(!!storedToken); + const savedToken = localStorage.getItem(FCM_TOKEN_KEY); + setNotificationEnabled(!!savedToken); }, []); const handleNotificationToggle = async () => { try { const newState = !isNotificationEnabled; + console.log("현재 알림 상태:", isNotificationEnabled); + console.log("토글 후 상태가 될 값:", newState); if (newState) { - // 알림 켜기 + // 알림 활성화 const permission = await Notification.requestPermission(); - if (permission === "granted") { - const fcmToken = await requestForToken(); - if (fcmToken) { - await updateFcm({ - token: fcmToken, - isNotificatioinEnabled: true, - }); - localStorage.setItem("fcmToken", fcmToken); - setNotificationEnabled(true); - console.log("알람이 활성화 되었습니다."); - } - } else { + if (permission !== "granted") { alert( "알림 권한이 필요합니다. 브라우저 설정에서 알림을 허용해주세요.", ); - setNotificationEnabled(false); + return; + } + + // FCM 토큰 새로 발급 + const newToken = await requestForToken(); + if (!newToken) { + throw new Error("알림 토큰 발급 실패"); } + + console.log("새로운 FCM 토큰:", newToken); + + // 토큰으로 알림 활성화 요청 + await fcmUpdateMutation.mutateAsync({ + token: newToken, + isNotificationEnabled: true, + }); + + // 성공 시 토큰 저장 및 상태 업데이트 + localStorage.setItem(FCM_TOKEN_KEY, newToken); + setNotificationEnabled(true); + console.log("알림이 활성화되었습니다."); } else { - // 알람 끄기 - const currentToken = localStorage.getItem("fcmToken"); + // 알림 비활성화 + const currentToken = localStorage.getItem(FCM_TOKEN_KEY); if (currentToken) { - await updateFcm({ + await fcmUpdateMutation.mutateAsync({ token: currentToken, - isNotificatioinEnabled: false, + isNotificationEnabled: false, }); - localStorage.removeItem("fcmToken"); + localStorage.removeItem(FCM_TOKEN_KEY); + setNotificationEnabled(false); + console.log("알림이 비활성화되었습니다."); } - setNotificationEnabled(false); - console.log("알람이 비활성화 되었습니다."); } - } catch (error) { + } catch (error: any) { console.error("알림 설정 변경 중 오류 발생:", error); - alert("알림 설정 변경에 실패했습니다."); - // 상태를 이전 값으로 되돌림 + console.error("서버 응답:", error.response?.data); + console.error("상태 코드:", error.response?.status); + alert( + `알림 설정 변경에 실패했습니다. ${error.response?.data?.message || error.message}`, + ); setNotificationEnabled(!isNotificationEnabled); } }; @@ -207,11 +223,8 @@ export default function MyPage() { }; const handleLogout = () => { - // 로그아웃 시 상태와 로컬 스토리지 초기화 setAuthState({ isAuthenticated: false }); localStorage.removeItem("authState"); - - // 헤더의 인증 토큰 제거 navigate(RouterPath.HOME); }; @@ -220,7 +233,6 @@ export default function MyPage() { 마이페이지 - {/* 프로필 카드 */} - {/* 정보 카드 그리드 */} - {/* 구독정보 카드 */} - {/* 알림설정 카드 */} + - {!isNotificationEnabled && ( + {fcmUpdateMutation.isError && (
+ style={{ color: "red", fontSize: "12px", marginTop: "8px" }} + > + 알림 설정 변경에 실패했습니다. +
)}
- {/* 회원 탈퇴 버튼 */}