From d106f80516489f796f7e61f90a69e871d21366c2 Mon Sep 17 00:00:00 2001
From: ptyoiy <56474564+ptyoiy@users.noreply.github.com>
Date: Wed, 3 Jul 2024 17:01:50 +0900
Subject: [PATCH] feat: add notification click event, token refresh feature
---
public/firebaseSW.js | 72 +++++-----
src/App.tsx | 84 ++++++++++--
src/api/axios/alarm.ts | 10 +-
src/api/axios/index.ts | 1 +
src/api/query/alarm.ts | 2 +-
src/api/query/index.ts | 1 -
src/api/query/user.ts | 43 ++----
src/components/Login/index.tsx | 4 +-
src/components/Modal/MainModal/index.tsx | 13 +-
.../PaperList/AlarmSettingList/index.tsx | 25 ----
src/context/index.tsx | 95 ++++++++++++-
src/context/setting.ts | 129 ------------------
src/context/user.d.ts | 8 +-
src/pages/MainPage/EventCard/index.tsx | 8 +-
src/pages/MyPage/index.tsx | 106 +++++---------
src/pages/TerracePage/index.tsx | 60 +-------
src/util/firebaseCloudMessage.ts | 23 +---
17 files changed, 273 insertions(+), 411 deletions(-)
delete mode 100644 src/components/PaperList/AlarmSettingList/index.tsx
delete mode 100644 src/context/setting.ts
diff --git a/public/firebaseSW.js b/public/firebaseSW.js
index a961e10..3d12fe0 100644
--- a/public/firebaseSW.js
+++ b/public/firebaseSW.js
@@ -1,5 +1,6 @@
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js');
+
const firebaseConfig = {
apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
authDomain: 'test-csey.firebaseapp.com',
@@ -12,47 +13,42 @@ const firebaseConfig = {
const app = firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging(app);
-console.log('#@#object');
-
-self.addEventListener('push', (event) => {
- console.log("Push");
- const options = {
- body: event.data.text(),
- icon: 'icon.png',
- badge: 'badge.png',
- };
- event.waitUntil(self.registration.showNotification('Hi-Notification', options));
-});
-
-// messaging
-// .getToken({
-// vapidKey:
-// 'BBHaXtLh_pZUn89NG0Jp1J_1N0wGS2R_9vllG20dfYsJ4dF5ZmTDofKD0HbBhoeZYhngL3YQ0GHUxRnnXpAkoko',
-// })
-// .then((currentToken) => {
-// if (currentToken) {
-// // Send the token to your server and update the UI if necessary
-// // ...
-// console.log({ currentToken });
-// } else {
-// // Show permission request UI
-// console.log('No registration token available. Request permission to generate one.');
-// // ...
-// }
-// })
-// .catch((err) => {
-// console.log('An error occurred while retrieving token. ', err);
-// // ...
-// });
messaging.onBackgroundMessage((payload) => {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
- const notificationTitle = 'Background Message Title';
- const notificationOptions = {
- body: 'Background Message body.',
- icon: '/firebase-logo.png',
- };
+ self.registration.showNotification(payload.notification.title, {
+ body: payload.notification.body,
+ data: payload.fcmOptions.link,
+ });
+});
- self.registration.showNotification(notificationTitle, notificationOptions);
+self.addEventListener('notificationclick', (event) => {
+ const { data } = event.notification;
+ const [type, id] = data.split('=');
+ let clientFound = false;
+ console.log({ event });
+ event.notification.close();
+ event.waitUntil(
+ clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
+ console.log({ clients, clientList });
+ for (let i = 0; i < clientList.length; i++) {
+ const client = clientList[i];
+ if (client.visibilityState === 'visible') {
+ client.postMessage({
+ action: 'notificationClick',
+ data
+ });
+ clientFound = true;
+ break;
+ }
+ }
+ if (!clientFound) {
+ clients
+ .openWindow(
+ `https://localhost:8080/${type === 'events' ? 'main' : 'notification'}?${type}=${id}`
+ );
+ }
+ })
+ );
});
diff --git a/src/App.tsx b/src/App.tsx
index ad9098d..64a314f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,9 +1,11 @@
/* eslint-disable react/jsx-props-no-spreading */
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { initializeApp } from '@firebase/app';
import { Modal } from '@mui/material';
+import { Unsubscribe, getMessaging, onMessage } from 'firebase/messaging';
import { useContext, useEffect } from 'react';
-import { Navigate, Route, Routes, useSearchParams } from 'react-router-dom';
+import { Navigate, Route, Routes, useNavigate, useSearchParams } from 'react-router-dom';
import Wrapper from './AppStyles';
-import { useUserInfoQuery } from './api/query';
import BottomNavbar from './components/BottomNavbar';
import HeaderLogo from './components/HeaderLogo';
import { userContext } from './context';
@@ -15,37 +17,91 @@ import NoticePage from './pages/NoticePage';
import { SetMajor, Signup } from './pages/SignupPage';
import TerracePage from './pages/TerracePage';
+const firebaseConfig = {
+ apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
+ authDomain: 'test-csey.firebaseapp.com',
+ projectId: 'test-csey',
+ storageBucket: 'test-csey.appspot.com',
+ messagingSenderId: '491214968405',
+ appId: '1:491214968405:web:08e469439f00c3c07ba6dc',
+ measurementId: 'G-JGZP14R2TG',
+};
+const app = initializeApp(firebaseConfig);
+
function App() {
console.log('APP rerender');
- const { userData, dispatch } = useContext(userContext);
+ const { userData } = useContext(userContext);
const { modal, modalDispatch } = useContext(ModalContext);
const [, setSearchParams] = useSearchParams();
- useUserInfoQuery(userData, dispatch);
+ const navigate = useNavigate();
const { Component, props } = modal;
const handleModalClose = () => {
setSearchParams({});
modalDispatch({ payload: 'close' });
};
useEffect(() => {
+ let unsub: Unsubscribe;
+ let registration: ServiceWorkerRegistration;
if ('serviceWorker' in navigator) {
- navigator.serviceWorker
- .register('/public/firebaseSW.js')
- .then((registration) => {
- console.log('Service Worker registered with scope:', registration.scope);
- })
- .catch((error) => {
- console.log('Service Worker registration failed:', error);
- })
- .finally(() => {
- console.log('finally');
+ // TODO: onMessage에서 notification 나오게 만드는 코드 작성하기
+ const regist = async () => {
+ console.log(
+ 'unreg:',
+ await navigator.serviceWorker.getRegistration('https://localhost:8080/public/')
+ );
+ registration = await navigator.serviceWorker.register('/public/firebaseSW.js');
+ if (registration) {
+ console.log('registration success', registration.scope, registration.active);
+ registration.showNotification('registration success', {
+ body: 'registration success body',
+ data: 'naver.com',
+ });
+ }
+ navigator.serviceWorker.addEventListener('message', (event) => {
+ if (!event.data.action) return;
+ const { data, action } = event.data;
+ console.log('data:', event.data);
+ const [type, id] = (data as string).split('=');
+ switch (action) {
+ case 'notificationClick':
+ navigate(`/${type === 'events' ? 'main' : 'notification'}`);
+ modalDispatch({
+ payload: 'mainModal',
+ props: {
+ postId: +id,
+ type: type as any
+ }
+ });
+ break;
+ // skip default
+ }
+ });
+ const messaging = getMessaging(app);
+ unsub = onMessage(messaging, (payload) => {
+ console.log({ payload });
+ registration.showNotification(payload.notification.title, {
+ body: payload.notification.body,
+ data: payload.fcmOptions.link,
+ });
});
+ };
+ regist();
}
+ return () => {
+ if (unsub) {
+ console.log('clean');
+ unsub();
+ registration.unregister();
+ }
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
} />
+ } />
} />
} />
} />
diff --git a/src/api/axios/alarm.ts b/src/api/axios/alarm.ts
index 98c453d..0b02047 100644
--- a/src/api/axios/alarm.ts
+++ b/src/api/axios/alarm.ts
@@ -6,11 +6,17 @@ export type SetAlarmBody = {
fcmToken: string;
};
-function setAlarm({ alarmData, fcmToken }: SetAlarmBody) {
+export function setAlarm({ alarmData, fcmToken }: SetAlarmBody) {
console.log('set alarm called');
const loginInfo = JSON.parse(localStorage.getItem('info'));
return axios.put(`/${loginInfo.userId}/alarms`, {
alarmData, fcmToken
});
}
-export default setAlarm;
+
+export function retireToken(token: string) {
+ console.log('retireToken called');
+ return axios.post('/fcm-token/retire', {
+ token
+ });
+}
diff --git a/src/api/axios/index.ts b/src/api/axios/index.ts
index 0174422..fad0310 100644
--- a/src/api/axios/index.ts
+++ b/src/api/axios/index.ts
@@ -3,4 +3,5 @@ import { getUserInfo, kakaoLogout, login } from './user';
axios.defaults.baseURL = '/api';
axios.defaults.withCredentials = true;
+
export { getUserInfo, kakaoLogout, login };
diff --git a/src/api/query/alarm.ts b/src/api/query/alarm.ts
index c7b1bcd..31396a4 100644
--- a/src/api/query/alarm.ts
+++ b/src/api/query/alarm.ts
@@ -1,6 +1,6 @@
/* eslint-disable max-len */
import { useMutation } from '@tanstack/react-query';
-import setAlarm from '../axios/alarm';
+import { setAlarm } from '../axios/alarm';
const useAlarmMutation = () => {
// const queryClient = useQueryClient();
diff --git a/src/api/query/index.ts b/src/api/query/index.ts
index 3caa869..e0a6df7 100644
--- a/src/api/query/index.ts
+++ b/src/api/query/index.ts
@@ -7,7 +7,6 @@ import { useLoginQuery, useUserInfoQuery } from './user';
export {
useAlarmMutation,
- useNoticeByIdQuery as useAlertByIdQuery,
useAlertsQuery,
useEventBookmarkMutation,
useEventByIdQuery,
diff --git a/src/api/query/user.ts b/src/api/query/user.ts
index 76956f9..bc4b746 100644
--- a/src/api/query/user.ts
+++ b/src/api/query/user.ts
@@ -1,10 +1,9 @@
-import { useQuery, useQueryClient } from '@tanstack/react-query';
-import { AxiosError } from 'axios';
-import { useEffect } from 'react';
-import { Action, UserData } from '../../context';
+import { useQuery } from '@tanstack/react-query';
+import axios, { AxiosError } from 'axios';
+import { UserData } from '../../context';
import { User } from '../../context/user.d';
import { login } from '../axios';
-import { getAccessToken, getUserInfo } from '../axios/user';
+import { getUserInfo } from '../axios/user';
export const useLoginQuery = (id: number, kakao_accessToken: string) => {
const info = useQuery({
@@ -17,19 +16,17 @@ export const useLoginQuery = (id: number, kakao_accessToken: string) => {
return info;
};
-export const useUserInfoQuery = (userData: UserData, dispatch: React.Dispatch) => {
+export const useUserInfoQuery = (userData: UserData) => {
const { userId, isAuthenticated } = userData;
- const queryClient = useQueryClient();
+ // const queryClient = useQueryClient();
const userInfo = useQuery({
queryKey: ['userInfo'],
queryFn: () => getUserInfo(userId),
- enabled: isAuthenticated,
+ enabled: isAuthenticated && Boolean((axios.interceptors.response as any).handlers.length),
staleTime: Infinity,
select(data) {
- const {
- alarm, bookmark, eventLike, noticeLike, noticeRead,
- } = data;
- return new User(userData, alarm, bookmark, noticeRead, noticeLike, eventLike);
+ const { bookmark, eventLike, noticeLike, noticeRead } = data;
+ return new User(userData, bookmark, noticeRead, noticeLike, eventLike);
},
retry(_failureCount, error: AxiosError) {
// 401 == accessToken이 없음
@@ -40,28 +37,10 @@ export const useUserInfoQuery = (userData: UserData, dispatch: React.Dispatch 재시도 종료 후 error 플래그on
const maxRetry = _failureCount === 3;
const retryStatus = ![401, 419].some((errorCode) => errorCode === error?.response?.status);
+ console.count('retry');
+ console.log({ retryStatus });
return maxRetry || retryStatus;
},
});
- const { isError, error } = userInfo;
- useEffect(() => {
- const fetchNewAccessToken = async () => {
- try {
- const refreshToken = localStorage.getItem('refreshToken');
- await getAccessToken(refreshToken);
- // 쿠키에 저장된 새로운 AccessToken으로 이전에 실패한 쿼리 다시 실행
- await queryClient.invalidateQueries({ queryKey: ['userInfo'] });
- } catch (_error) {
- // RefreshToken이 만료된 경우 context정보 삭제
- dispatch({ payload: 'logout' });
- }
- };
-
- if (isError) {
- fetchNewAccessToken();
- }
- if (error?.status === 401) dispatch({ payload: 'logout' });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isError]);
return userInfo;
};
diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx
index 7a6c747..ae93221 100644
--- a/src/components/Login/index.tsx
+++ b/src/components/Login/index.tsx
@@ -2,8 +2,8 @@ import { memo, useContext, useEffect, useRef, useState } from 'react';
import KakaoLogin from 'react-kakao-login';
import { Props } from 'react-kakao-login/lib/types';
import { useLoginQuery } from '../../api/query';
-import { userContext } from '../../context';
import ChatYellow from '../../assets/Img/ChatYellow.png';
+import { userContext } from '../../context';
import * as s from '../../pages/SignupPage/styles';
type LoginButtonProps = {
@@ -36,7 +36,7 @@ const LoginButton = memo(({ showLogin }: LoginButtonProps) => {
dispatch({
payload: 'authenticate',
data: {
- info: { userId: id, name, major },
+ info: { userId: id, name, major, fcmToken: localStorage.getItem('fcmToken') },
refreshToken: data.refreshToken,
},
});
diff --git a/src/components/Modal/MainModal/index.tsx b/src/components/Modal/MainModal/index.tsx
index a278725..2caf468 100644
--- a/src/components/Modal/MainModal/index.tsx
+++ b/src/components/Modal/MainModal/index.tsx
@@ -1,9 +1,7 @@
/* eslint-disable consistent-return */
import { forwardRef, useContext, useState } from 'react';
-import { Modal } from '@mui/material';
import { useSearchParams } from 'react-router-dom';
import {
- useAlertByIdQuery,
useEventBookmarkMutation,
useEventByIdQuery,
useEventLikeMutation,
@@ -24,20 +22,19 @@ import ShareIcon from '../../../assets/Icons/ShareIcon.png';
import CloseBtnSrc from '../../../assets/Icons/modalCloseBtn.png';
import { userContext } from '../../../context';
import { formatDate } from '../../../util/dateUtil';
-import ModalImage from '../ModalImage';
import Toast from '../../Toast';
+import ModalImage from '../ModalImage';
import Stepper from '../stepper';
import * as s from './styles';
// eslint-disable-next-line import/no-cycle
-import { ModalViewType } from '../../../pages/NoticePage';
import { MainModalProps, ModalContext } from '../../../context/modal';
+import { ModalViewType } from '../../../pages/NoticePage';
function useFetchDataByType(type: ModalViewType) {
switch (type) {
case 'events':
return useEventByIdQuery;
case 'alerts':
- return useAlertByIdQuery;
case 'notices':
return useNoticeByIdQuery;
// skip default
@@ -59,10 +56,10 @@ const MainModal = forwardRef((_props, _ref) => {
const { modal, modalDispatch } = useContext(ModalContext);
const { type, postId: eventId } = (modal.props as MainModalProps);
- const { userData, dispatch } = useContext(userContext);
+ const { userData } = useContext(userContext);
const { userId } = userData;
const { data, isPending } = useFetchDataByType(type)(eventId.toString());
- const { data: user } = useUserInfoQuery(userData, dispatch);
+ const { data: user } = useUserInfoQuery(userData);
const isLike = user?.[type === 'events' ? 'eventsLike' : 'noticesLike']?.[eventId];
const [toast, setToast] = useState(false);
const [curImgIdx, setCurImgIdx] = useState(0);
@@ -196,6 +193,6 @@ const MainModal = forwardRef((_props, _ref) => {
{toast && }
>
);
-})
+});
export default MainModal;
diff --git a/src/components/PaperList/AlarmSettingList/index.tsx b/src/components/PaperList/AlarmSettingList/index.tsx
deleted file mode 100644
index 0c6cbbb..0000000
--- a/src/components/PaperList/AlarmSettingList/index.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { AlarmSettingCardContents } from '../../../context/user';
-import {
- Content,
- ContentGroup, ContentRow,
- Description,
- Meta,
-} from '../styles';
-
-function AlarmSettingList({ content }: { content: AlarmSettingCardContents[] }) {
- if (!content) return <>loading>;
- return (
-
- {content.map((v) => (
-
- v.eventHandler(e)}>
- {v.meta}
- {v?.description}
-
-
- ))}
-
- );
-}
-
-export default AlarmSettingList;
diff --git a/src/context/index.tsx b/src/context/index.tsx
index fb65b01..482ca19 100644
--- a/src/context/index.tsx
+++ b/src/context/index.tsx
@@ -1,5 +1,10 @@
+import axios, { AxiosError } from 'axios';
import { Dispatch, ReactNode, createContext, useEffect, useMemo, useReducer } from 'react';
import { useNavigate } from 'react-router-dom';
+import { retireToken } from '../api/axios/alarm';
+import { getAccessToken } from '../api/axios/user';
+import { useUserInfoQuery } from '../api/query';
+import requestFCMToken from '../util/firebaseCloudMessage';
interface SetLoginInfoAction {
payload: 'authenticate';
@@ -16,17 +21,40 @@ interface LogoutAction {
payload: 'logout';
data?: undefined;
}
+interface SetFcmToken {
+ payload: 'setFcmToken';
+ data: string;
+}
export type UserData = {
isAuthenticated: boolean;
userId: number;
name: string;
major: SetMajorAction['data'];
+ fcmToken: string;
};
-export type Action = SetLoginInfoAction | SetMajorAction | LogoutAction;
+export type Action = SetLoginInfoAction | SetMajorAction | LogoutAction | SetFcmToken;
export const userContext = createContext<{ userData: UserData; dispatch: Dispatch }>(null);
const info = localStorage.getItem('info');
const parsedInfo = info && JSON.parse(info);
+const fcmToken = localStorage.getItem('fcmToken');
+const alarm = localStorage.getItem('alarm');
+if (!alarm) {
+ localStorage.setItem(
+ 'alarm',
+ JSON.stringify({
+ alarm_push: false,
+ event_push: false,
+ events_timer: 24,
+ events_form: '전체',
+ events_post: false,
+ major_schedule_push: false,
+ major_schedule_post: false,
+ notice_push: false,
+ alerts_push: false,
+ })
+ );
+}
export function UserProvider({ children }: { children: ReactNode }) {
const [userData, dispatch] = useReducer(reducer, {
@@ -34,10 +62,69 @@ export function UserProvider({ children }: { children: ReactNode }) {
userId: parsedInfo?.userId,
name: parsedInfo?.name,
major: parsedInfo?.major,
+ fcmToken,
});
+ const userInfo = useUserInfoQuery(userData);
+
console.log({ userData });
const navigate = useNavigate();
const value = useMemo(() => ({ userData, dispatch }), [userData]);
+
+ // token 갱신
+ useEffect(() => {
+ axios.interceptors.response.use(
+ (response) => response,
+ async (error: AxiosError) => {
+ const originalRequest = error.config;
+ console.log({ originalRequest });
+ switch (error.response.status) {
+ // access 없음
+ // access 만료시 갱신
+ case 401:
+ case 419: {
+ const refreshToken = localStorage.getItem('refreshToken');
+ if (refreshToken) {
+ await getAccessToken(refreshToken);
+ return axios(originalRequest);
+ }
+ dispatch({ payload: 'logout' });
+ break;
+ }
+ // refresh 없음
+ case 403: {
+ dispatch({ payload: 'logout' });
+ break;
+ }
+ // skip default
+ }
+ return Promise.reject(error);
+ }
+ );
+ }, [dispatch]);
+
+ // fcm토큰 갱신
+ useEffect(() => {
+ const checkFcmToken = async () => {
+ const prevToken = fcmToken;
+ const currentToken = await requestFCMToken();
+ if (!currentToken) {
+ console.warn('request FCM Token failed');
+ return;
+ }
+ if (prevToken !== currentToken) {
+ if (prevToken) await retireToken(prevToken);
+ dispatch({
+ payload: 'setFcmToken',
+ data: currentToken,
+ });
+ }
+ };
+ console.log({ userInfo });
+ if (userInfo.isSuccess) checkFcmToken();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [userInfo?.isSuccess]);
+
+ // 초기화면(회원가입) navigate
useEffect(() => {
if (!userData.isAuthenticated && !userData.major) {
navigate('/signup');
@@ -65,7 +152,7 @@ function reducer(state: UserData, { payload, data }: Action): UserData {
}
case 'setMajor': {
const { userId, name } = state;
- localStorage.setItem('info', JSON.stringify({ userId, name, major: data }));
+ localStorage.setItem('info', JSON.stringify({ userId, name, fcmToken, major: data }));
return { ...state, major: data };
}
case 'logout': {
@@ -74,6 +161,10 @@ function reducer(state: UserData, { payload, data }: Action): UserData {
localStorage.removeItem('refreshToken');
return { ...state, userId: undefined, name: undefined, isAuthenticated: false };
}
+ case 'setFcmToken': {
+ localStorage.setItem('fcmToken', data);
+ return { ...state, fcmToken: data };
+ }
// skip default
}
}
diff --git a/src/context/setting.ts b/src/context/setting.ts
deleted file mode 100644
index 3d15670..0000000
--- a/src/context/setting.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-/* eslint-disable max-classes-per-file */
-import { UseMutationResult } from '@tanstack/react-query';
-import { AxiosResponse } from 'axios';
-import { ChangeEvent } from 'react';
-import { SetAlarmBody } from '../api/axios/alarm';
-import { AlarmSettingCardContents, IAlarm, SettingCardData, SettingCardHeader } from './user';
-
-type Mutation = UseMutationResult, Error, SetAlarmBody, unknown>;
-const textMap = {
- alarm_push: '전체 알림 켜기',
- event_push: '행사 알림 켜기',
- events_timer: '알림 시간',
- events_form: '알림 형태',
- events_post: '행사 등록 알림 켜기',
- major_schedule_push: '일정 당일 알림 켜기',
- major_schedule_post: '일정 등록 알림 켜기',
- notice_push: '일반 공지 알림 켜기',
- alerts_push: '긴급 공지 알림',
-} as const;
-
-export class AlarmSettingContents {
- private alarm_push: AlarmGroup;
-
- private event_push: AlarmGroup;
-
- private major_push: AlarmGroup;
-
- private notice_push: AlarmGroup;
-
- private entireAlarm: boolean;
-
- constructor(alarmData: IAlarm) {
- // 개별 알람
- // 전체
- const alarmPush = new AlarmContent(undefined, 'alarm_push', alarmData);
- this.entireAlarm = !alarmData.alarm_push;
- // 행사 알림
- const eventsTimer = new AlarmContent(undefined, 'events_timer', alarmData, `시작 ${alarmData.events_timer}시간 전`);
- const eventsForm = new AlarmContent(undefined, 'events_form', alarmData, `${alarmData.events_form}`);
- const eventPush = new AlarmContent(alarmPush, 'event_push', alarmData, undefined, eventsTimer, eventsForm);
- // 행사 등록 알림
- const eventsPost = new AlarmContent(alarmPush, 'events_post', alarmData);
- // 학사일정
- const majorSchedulePost = new AlarmContent(alarmPush, 'major_schedule_post', alarmData);
- const majorSchedulePush = new AlarmContent(alarmPush, 'major_schedule_push', alarmData);
- // 공지
- const noticePush = new AlarmContent(alarmPush, 'notice_push', alarmData);
- const alertsPush = new AlarmContent(alarmPush, 'alerts_push', alarmData);
-
- // 알람 그룹
- this.alarm_push = new AlarmGroup('전체 알림', alarmPush);
- this.event_push = new AlarmGroup('행사 알림', eventPush, eventsPost);
- this.major_push = new AlarmGroup('학사일정 알림', majorSchedulePost, majorSchedulePush);
- this.notice_push = new AlarmGroup('공지 알림', noticePush, alertsPush);
-
- console.log('AlarmSettingContents: ', this);
- this.alarm_push = new AlarmGroup('전체 알림', alarmPush);
- this.event_push = new AlarmGroup('행사 알림', eventPush, eventsPost);
- this.major_push = new AlarmGroup('학사일정 알림', majorSchedulePost, majorSchedulePush);
- this.notice_push = new AlarmGroup('공지 알림', noticePush, alertsPush);
- }
-
- getGroups() {
- return [this.alarm_push, this.event_push, this.major_push, this.notice_push];
- }
-}
-
-export class AlarmGroup implements SettingCardData {
- header: SettingCardHeader;
-
- contents?: AlarmContent[];
-
- constructor(header: string, ...contents: AlarmContent[]) {
- this.header = {
- title: header,
- };
- this.contents = contents;
- }
-
- setMutations(mutation: Mutation) {
- this.contents.forEach((c) => {
- c.setMutation(mutation);
- });
- }
-
- findContent(key: string) {
- return this.contents.find((c) => c.key === key);
- }
-}
-
-export class AlarmContent implements AlarmSettingCardContents {
- meta: string;
-
- value?: any;
-
- hasCheck?: boolean;
-
- child?: AlarmContent[];
-
- description?: string;
-
- disabled?: boolean;
-
- eventHandler?: (e?: ChangeEvent) => void;
-
- constructor(
- public parent: AlarmContent,
- public key: keyof typeof textMap,
- alarmData: IAlarm,
- description?: string,
- ...child: AlarmContent[]
- ) {
- this.value = alarmData[this.key];
- this.meta = textMap[key];
- this.hasCheck = !description;
- this.description = description;
- this.child = child;
- if (child) {
- // eslint-disable-next-line no-param-reassign
- child.forEach((c) => { c.parent = parent; });
- }
- }
-
- setMutation(mutation: Mutation) {
- this.eventHandler = (e: ChangeEvent) => {
- // mutation.mutate({ [this.key]: this.hasCheck ? e.target.checked : e.target.value });
- };
- }
-}
diff --git a/src/context/user.d.ts b/src/context/user.d.ts
index a103522..978f40d 100644
--- a/src/context/user.d.ts
+++ b/src/context/user.d.ts
@@ -16,7 +16,10 @@ export interface SettingCardContents {
export interface AlarmSettingCardContents extends SettingCardContents {
hasCheck?: boolean; // true: 체크박스 표시, false: └─> 표시
child?: SettingCardContents[];
- eventHandler?: (e: string) => (e: ChangeEvent | MouseEvent) => void;
+ eventHandler?: (
+ e: ChangeEvent | MouseEvent,
+ name: string
+ ) => void;
disabled?: boolean;
}
@@ -30,8 +33,6 @@ export type SettingCardData = {
export class User {
public user: IUser;
- public alarm: IAlarm;
-
public bookmark: IBookmark;
public noticeRead: IRead;
@@ -42,7 +43,6 @@ export class User {
constructor(
user: UserData,
- alarm?: IAlarm,
bookmark?: IBookmark,
reads?: IRead,
noticesLike?: INoticeLike,
diff --git a/src/pages/MainPage/EventCard/index.tsx b/src/pages/MainPage/EventCard/index.tsx
index 2447e44..bf86172 100644
--- a/src/pages/MainPage/EventCard/index.tsx
+++ b/src/pages/MainPage/EventCard/index.tsx
@@ -3,10 +3,10 @@ import { useEventBookmarkMutation, useUserInfoQuery } from "../../../api/query";
import AlarmOffIcon from "../../../assets/Icons/AlarmOff.png";
import AlarmOnIcon from "../../../assets/Icons/AlarmOn.png";
import { userContext } from "../../../context";
+import { ModalContext } from "../../../context/modal";
import { EventType } from "../../../types";
import { leftDate } from "../../../util/dateUtil";
import * as s from "./styles";
-import { ModalContext } from "../../../context/modal";
// react 컴포넌트는 무조건 매개변수를 하나 갖고 있음(props)
// props의 속성은 자유자재로 가능
@@ -16,10 +16,10 @@ function EventCard({
}: {
event: EventType;
}) {
- const { userData, dispatch } = useContext(userContext);
+ const { userData } = useContext(userContext);
const { modalDispatch } = useContext(ModalContext);
- const { userId, isAuthenticated } = userData;
- const { data } = useUserInfoQuery(userData, dispatch);
+ const { userId } = userData;
+ const { data } = useUserInfoQuery(userData);
const alarmOn = data?.bookmark?.includes(id.toString());
const bookmarkMutation = useEventBookmarkMutation(
userId?.toString(),
diff --git a/src/pages/MyPage/index.tsx b/src/pages/MyPage/index.tsx
index 390eba9..4dc964b 100644
--- a/src/pages/MyPage/index.tsx
+++ b/src/pages/MyPage/index.tsx
@@ -1,14 +1,13 @@
/* eslint-disable react/no-unused-prop-types */
/* eslint-disable react/jsx-props-no-spreading */
import { useQueryClient } from '@tanstack/react-query';
-import { memo, useContext, useEffect, useState } from 'react';
+import { memo, useContext, useState } from 'react';
import { ArrowLeft, Settings } from 'react-feather';
import { useNavigate } from 'react-router-dom';
import { useAlarmMutation } from '../../api/query';
import * as BottomNav from '../../components/BottomNavbar';
import PaperBox from '../../components/PaperBox';
import SettingList from '../../components/PaperList/SettingList';
-import { initializeApp } from '@firebase/app';
import {
AccountSection,
GuideSection,
@@ -17,7 +16,6 @@ import {
} from '../../components/SettingSection';
import { userContext } from '../../context';
import { ModalContext } from '../../context/modal';
-import { AlarmGroup } from '../../context/setting';
import { AlarmSettingCardContents, IAlarm } from '../../context/user';
import {
AlarmWrapper,
@@ -34,12 +32,10 @@ import {
Title,
Wrapper,
} from './styles';
-import { requestFirebaseNotificationPermission } from '../../util/firebaseCloudMessage';
-import { getMessaging, onMessage } from 'firebase/messaging';
/** My - 설정페이지 */
export function Setting() {
- const { modal, modalDispatch } = useContext(ModalContext);
+ const { modalDispatch } = useContext(ModalContext);
const { userData, dispatch } = useContext(userContext);
const navigate = useNavigate();
const client = useQueryClient();
@@ -65,16 +61,7 @@ export function Setting() {
);
}
-const firebaseConfig = {
- apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
- authDomain: 'test-csey.firebaseapp.com',
- projectId: 'test-csey',
- storageBucket: 'test-csey.appspot.com',
- messagingSenderId: '491214968405',
- appId: '1:491214968405:web:08e469439f00c3c07ba6dc',
- measurementId: 'G-JGZP14R2TG',
-};
-const app = initializeApp(firebaseConfig);
+
const textMap = {
alarm_push: '전체 알림 켜기',
event_push: '행사 알림 켜기',
@@ -84,59 +71,32 @@ const textMap = {
major_schedule_push: '일정 당일 알림 켜기',
major_schedule_post: '일정 등록 알림 켜기',
notice_push: '일반 공지 알림 켜기',
- alerts_push: '긴급 공지 알림',
+ alerts_push: '긴급 공지 알림 켜기',
} as const;
/** My - 알림 설정 페이지 */
export function AlarmSetting() {
- const { userData, dispatch } = useContext(userContext);
+ const {
+ userData: { fcmToken },
+ } = useContext(userContext);
const navigate = useNavigate();
const alarmMutation = useAlarmMutation();
- const [alarmData, setAlarmData] = useState({
- alarm_push: false,
- event_push: false,
- events_timer: 24,
- events_form: '전체',
- events_post: false,
- major_schedule_push: false,
- major_schedule_post: false,
- notice_push: false,
- alerts_push: false,
- });
- const [fcmToken, setFcmToken] = useState('');
- useEffect(() => {
- const subscribe = async () => {
- const token = await requestFirebaseNotificationPermission();
- if (token) {
- setFcmToken(token);
- }
- };
-
- subscribe();
-
- // const messaging = getMessaging(app);
- // const unsub = onMessage(messaging, (payload) => {
- // console.log('Message received. ', payload);
- // });
- // return () => unsub();
- }, []);
- const handleChange = (name) => (e) => {
+ const [alarmData, setAlarmData] = useState(
+ JSON.parse(localStorage.getItem('alarm'))
+ );
+ const handleChange = (e, name: string) => {
const { value, type, checked } = e.target;
const next = {
...alarmData,
[name]: type === 'checkbox' ? checked : value,
};
- setAlarmData(next);
+ setAlarmData(() => {
+ localStorage.setItem('alarm', JSON.stringify(next));
+ return next;
+ });
alarmMutation.mutate({ alarmData: next, fcmToken });
};
- const handleTokenChange = (e) => {
- setFcmToken(e.target.value);
- };
-
- const handleSubmit = async (e) => {
- e.preventDefault();
- };
return (
@@ -152,38 +112,38 @@ export function AlarmSetting() {
-
+
-
+
-
+
-
+
{textMap[meta]}
-
+ eventHandler(e, meta)}
+ />
);
}
@@ -279,7 +245,7 @@ function ContentChild({ eventHandler, meta, description }: AlarmSettingCardConte
return (
-
+ eventHandler(e, meta)}>
{textMap[meta]}
{description}
diff --git a/src/pages/TerracePage/index.tsx b/src/pages/TerracePage/index.tsx
index db1a0ce..1f6794a 100644
--- a/src/pages/TerracePage/index.tsx
+++ b/src/pages/TerracePage/index.tsx
@@ -1,66 +1,8 @@
-import { initializeApp } from '@firebase/app';
-import { getMessaging, onMessage } from 'firebase/messaging';
-import { useEffect, useState } from 'react';
-import { requestFirebaseNotificationPermission } from '../../util/firebaseCloudMessage';
import Wrapper from './styles';
-const firebaseConfig = {
- apiKey: 'AIzaSyAEU-CHEmYMLlSLnRLpmoABONwqHZGVREw',
- authDomain: 'test-csey.firebaseapp.com',
- projectId: 'test-csey',
- storageBucket: 'test-csey.appspot.com',
- messagingSenderId: '491214968405',
- appId: '1:491214968405:web:08e469439f00c3c07ba6dc',
- measurementId: 'G-JGZP14R2TG',
-};
-const app = initializeApp(firebaseConfig);
-
function TerracePage() {
- const [notification, setNotification] = useState({ title: '', body: '' });
- const topic = 'test'; // 구독할 주제
- const topics = [
- 'test'
- ];
- useEffect(() => {
- const subscribe = async () => {
- const token = await requestFirebaseNotificationPermission();
- // if (token) {
- // await subscribeToTopic(token, topic);
- // }
- };
-
- subscribe();
-
- const messaging = getMessaging(app);
- const unsub = onMessage(messaging, (payload) => {
- console.log('Message received. ', payload);
- setNotification({
- title: payload.notification.title,
- body: payload.notification.body,
- });
- });
- return () => unsub();
- }, []);
- const onClickHandler = () => {
- if (Notification.permission !== 'denied') {
- Notification.requestPermission().then((permission) => {
- if (permission === 'granted') {
- console.log('granted', navigator.serviceWorker);
- } else {
- alert('Notification denied');
- }
- });
- }
- };
return (
-
-
- Notification
- {notification.title}
- {notification.body}
-
+
);
}
diff --git a/src/util/firebaseCloudMessage.ts b/src/util/firebaseCloudMessage.ts
index 535dcc7..7de79f5 100644
--- a/src/util/firebaseCloudMessage.ts
+++ b/src/util/firebaseCloudMessage.ts
@@ -1,11 +1,11 @@
import { getMessaging, getToken } from 'firebase/messaging';
-export const requestFirebaseNotificationPermission = async () => {
+const requestFCMToken = async () => {
const messaging = getMessaging();
try {
const token = await getToken(messaging, {
serviceWorkerRegistration: await navigator.serviceWorker.getRegistration('https://localhost:8080/public/'),
- vapidKey: 'BBHaXtLh_pZUn89NG0Jp1J_1N0wGS2R_9vllG20dfYsJ4dF5ZmTDofKD0HbBhoeZYhngL3YQ0GHUxRnnXpAkoko'
+ vapidKey: 'BBHaXtLh_pZUn89NG0Jp1J_1N0wGS2R_9vllG20dfYsJ4dF5ZmTDofKD0HbBhoeZYhngL3YQ0GHUxRnnXpAkoko',
});
console.log('FCM Token:', token);
return token;
@@ -15,21 +15,4 @@ export const requestFirebaseNotificationPermission = async () => {
}
};
-export const subscribeToTopic = async (token, topic) => {
- try {
- const response = await fetch(`https://iid.googleapis.com/iid/v1/${token}/rel/topics/${topic}`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
-
- if (response.ok) {
- console.log(`Subscribed to topic: ${topic}`);
- } else {
- console.error('Error subscribing to topic:', response.statusText);
- }
- } catch (error) {
- console.error('Error subscribing to topic:', { error });
- }
-};
+export default requestFCMToken;