diff --git a/frontend/public/android/android-launchericon-144-144.png b/frontend/public/android/android-launchericon-144-144.png new file mode 100644 index 000000000..2c2911bc2 Binary files /dev/null and b/frontend/public/android/android-launchericon-144-144.png differ diff --git a/frontend/public/android/android-launchericon-192-192.png b/frontend/public/android/android-launchericon-192-192.png new file mode 100644 index 000000000..26b83adaa Binary files /dev/null and b/frontend/public/android/android-launchericon-192-192.png differ diff --git a/frontend/public/android/android-launchericon-48-48.png b/frontend/public/android/android-launchericon-48-48.png new file mode 100644 index 000000000..3dbbbedf9 Binary files /dev/null and b/frontend/public/android/android-launchericon-48-48.png differ diff --git a/frontend/public/android/android-launchericon-512-512.png b/frontend/public/android/android-launchericon-512-512.png new file mode 100644 index 000000000..000eab4f1 Binary files /dev/null and b/frontend/public/android/android-launchericon-512-512.png differ diff --git a/frontend/public/android/android-launchericon-72-72.png b/frontend/public/android/android-launchericon-72-72.png new file mode 100644 index 000000000..71916a46f Binary files /dev/null and b/frontend/public/android/android-launchericon-72-72.png differ diff --git a/frontend/public/android/android-launchericon-96-96.png b/frontend/public/android/android-launchericon-96-96.png new file mode 100644 index 000000000..5c8cd5937 Binary files /dev/null and b/frontend/public/android/android-launchericon-96-96.png differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json new file mode 100644 index 000000000..5a3247751 --- /dev/null +++ b/frontend/public/manifest.json @@ -0,0 +1,51 @@ +{ + "description": "", + "background_color": "#ffffff", + "dir": "ltr", + "display": "standalone", + "name": "dev.mouda", + "orientation": "portrait", + "scope": "/", + "short_name": "dev.mouda", + "start_url": "/", + "theme_color": "#ffffff", + "icons": [ + { + "src": "./android/android-launchericon-512-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "./android/android-launchericon-192-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "./android/android-launchericon-144-144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "./android/android-launchericon-96-96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "./android-launchericon-72-72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "./android-launchericon-48-48.png", + "sizes": "48x48", + "type": "image/png" + } + ], + "lang": "ko", + "display_override": [ + "window-controls-overlay" + ], + "categories": [ + "social" + ] +} diff --git a/frontend/src/apis/posts.ts b/frontend/src/apis/posts.ts index b18e32ec0..119e9805c 100644 --- a/frontend/src/apis/posts.ts +++ b/frontend/src/apis/posts.ts @@ -92,7 +92,7 @@ export const postPlease = async (please: PleaseInfoInput) => { }; export const postNotificationToken = async (currentToken: string) => { - await ApiClient.postWithLastDarakbangId('notification/register', { + await ApiClient.postWithAuth('notification/register', { token: currentToken, }); }; diff --git a/frontend/src/components/LoginForm/LoginForm.tsx b/frontend/src/components/LoginForm/LoginForm.tsx index fd5b8518e..e8fc8a82c 100644 --- a/frontend/src/components/LoginForm/LoginForm.tsx +++ b/frontend/src/components/LoginForm/LoginForm.tsx @@ -6,13 +6,10 @@ import { useNavigate } from 'react-router-dom'; import ROUTES from '@_constants/routes'; import { setToken } from '@_utils/tokenManager'; import { login } from '@_apis/auth'; -import { requestPermission } from '@_service/notification'; -import useServeToken from '@_hooks/mutaions/useServeToken'; // TODO: 로그인 기능 요구사항 변경 예정 export default function LoginForm() { const navigate = useNavigate(); - const { mutate } = useServeToken(); const [nickname, setNickname] = useState(''); const [isValid, setIsValid] = useState(false); @@ -26,7 +23,6 @@ export default function LoginForm() { const response = await login({ nickname }); setToken(response.data.accessToken); - requestPermission(mutate); navigate(ROUTES.main); }; diff --git a/frontend/src/index.html b/frontend/src/index.html index e7fff0037..13b3454b9 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -3,7 +3,7 @@ - 모우다 + 모우다
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 4d74436ba..09e090564 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -10,17 +10,6 @@ if (process.env.REACT_APP_GOOGLE_ANALYTICS) { ReactGA.initialize(process.env.REACT_APP_GOOGLE_ANALYTICS); } -if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register(`/firebase-messaging-sw.js`) - .then((registration) => { - console.log('Service Worker registered with scope:', registration.scope); - }) - .catch((error) => { - console.log('Service Worker registration failed:', error); - }); -} - Sentry.init({ dsn: process.env.SENTRY_DSN, integrations: [ @@ -30,7 +19,7 @@ Sentry.init({ // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled - tracePropagationTargets: ['http://43.202.67.25/'], + tracePropagationTargets: ['https://dev.mouda.site/'], // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index 9ce75c019..fb591b0c6 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -21,10 +21,11 @@ import SolidArrow from '@_components/Icons/SolidArrow'; import useMyDarakbangs from '@_hooks/queries/useMyDarakbang'; import useMyRoleInDarakbang from '@_hooks/queries/useMyDarakbangRole'; import { useNavigate } from 'react-router-dom'; - +import { requestPermission } from '@_service/notification'; +import useServeToken from '@_hooks/mutaions/useServeToken'; export default function MainPage() { const navigate = useNavigate(); - + const { mutate } = useServeToken(); const [currentTab, setCurrentTab] = useState('모임목록'); const [isDarakbangMenuOpened, setIsDarakbangMenuOpened] = useState(false); @@ -40,6 +41,7 @@ export default function MainPage() { const handleNotification = () => { navigate(ROUTES.notification); + requestPermission(mutate); }; const darakbangMenuOption = useMemo(() => { diff --git a/frontend/src/service/forgroundMessage.ts b/frontend/src/service/forgroundMessage.ts index fcd57f4a6..2ee7ffcda 100644 --- a/frontend/src/service/forgroundMessage.ts +++ b/frontend/src/service/forgroundMessage.ts @@ -1,32 +1,50 @@ import { getMessaging, onMessage } from 'firebase/messaging'; import { app } from './initFirebase'; -// Firebase Messaging 인스턴스 가져오기 -const messaging = getMessaging(app); +function initializeForegroundMessageHandling() { + const messaging = getMessaging(app); -onMessage(messaging, (payload) => { - console.log('포그라운드 알림 도착: ', payload); + onMessage(messaging, (payload) => { + console.log('포그라운드 알림 도착: ', payload); - const notificationTitle = payload.notification?.title; - const notificationOptions = { - body: payload.notification?.body, - icon: payload.notification?.icon, // 알림 아이콘 설정 - data: { link: payload.fcmOptions?.link || '/' }, // 알림 클릭 시 이동할 링크 설정 - }; + const notificationTitle = payload.notification?.title || '알림'; + const notificationOptions = { + body: payload.notification?.body || '', + icon: payload.notification?.icon, + data: { link: payload.fcmOptions?.link || '/' }, + }; - if (Notification.permission === 'granted') { - const notification = new Notification( - notificationTitle!, - notificationOptions, - ); + if (Notification.permission === 'granted') { + try { + const notification = new Notification( + notificationTitle, + notificationOptions, + ); - notification.onclick = function (event) { - event.preventDefault(); // 기본 동작 방지 (필요한 경우) + notification.onclick = function (event) { + event.preventDefault(); + window.open(notificationOptions.data.link, '_blank'); + }; + } catch (error) { + console.error('알림 생성 중 오류 발생:', error); + } + } else { + console.warn('알림 권한이 허용되지 않았습니다.'); + } + }); +} - // 알림 클릭 시 페이지 이동 - window.open(notificationOptions.data.link, '_blank'); - }; - } else { - console.warn('알림 권한이 허용되지 않았습니다.'); - } -}); +if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register(`/firebase-messaging-sw.js`) + .then((registration) => { + console.log('Service Worker registered with scope:', registration.scope); + initializeForegroundMessageHandling(); + }) + .catch((error) => { + console.log('Service Worker registration failed:', error); + }); +} else { + // 서비스 워커가 지원되지 않는 경우에도 포그라운드 메시지 처리를 초기화 + initializeForegroundMessageHandling(); +} diff --git a/frontend/webpack.common.js b/frontend/webpack.common.js index c8104bba6..b9444e0ff 100644 --- a/frontend/webpack.common.js +++ b/frontend/webpack.common.js @@ -21,6 +21,7 @@ module.exports = { plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, './src/index.html'), + manifest: path.resolve(__dirname, './public/manifest.json'), }), new webpack.DefinePlugin({ 'process.env': JSON.stringify(process.env), diff --git a/frontend/webpack.prod.js b/frontend/webpack.prod.js index cb1fcf3ad..674118ab4 100644 --- a/frontend/webpack.prod.js +++ b/frontend/webpack.prod.js @@ -2,6 +2,7 @@ const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const { sentryWebpackPlugin } = require('@sentry/webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = merge(common, { mode: 'production', @@ -47,5 +48,10 @@ module.exports = merge(common, { org: '2024-mouda', project: 'javascript-react', }), + new CopyWebpackPlugin({ + patterns: [ + { from: 'public', to: '' }, // public 폴더의 모든 파일을 dist 폴더의 루트로 복사 + ], + }), ], });