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 폴더의 루트로 복사
+ ],
+ }),
],
});