Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default [
{
rules: {
'prettier/prettier': 'warn',
'no-console': ['error', { allow: ['warn', 'error'] }],
},
},
tsConfig,
Expand Down
9 changes: 8 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import './App.css';

import { RouterProvider } from 'react-router-dom';

import { DeviceTokenProvider } from './providers/deviceTokenProvider';
import { alarmKeys } from './queryKey/queryKey';

import router from '@/routes/routes';

function App() {
return <RouterProvider router={router} />;
return (
<DeviceTokenProvider refetchKeys={[alarmKeys.all().queryKey]}>
<RouterProvider router={router} />
</DeviceTokenProvider>
);
}

export default App;
9 changes: 4 additions & 5 deletions src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ axiosInstance.interceptors.response.use(
const refreshResponse = await refresh();

if (refreshResponse.code === '200') {
console.log('refreshToken이 재발급 되었습니다');
isRedirecting = false;

return axiosInstance(error.config);
Expand All @@ -43,20 +42,20 @@ axiosInstance.interceptors.response.use(
if (axios.isAxiosError(errors)) {
const refreshError = error as AxiosError<IRefreshResponse>;
if (refreshError.response?.data.message === 'The token is null.') {
console.log('refreshToken이 없습니다. 로그인 페이지로 이동합니다.');
console.error('refreshToken이 없습니다. 로그인 페이지로 이동합니다.');
} else if (refreshError.response?.data.message === 'The token is invalid.') {
console.log('refreshToken이 만료되었습니다. 로그인 페이지로 이동합니다.');
console.error('refreshToken이 만료되었습니다. 로그인 페이지로 이동합니다.');
logout();
} else {
if (refreshError.response?.data.message === 'Incorrect password.') {
alert('Your email or password is incorrect.');
} else {
console.log('알 수 없는 오류가 발생했습니다', errors);
console.error('알 수 없는 오류가 발생했습니다', errors);
logout();
}
}
} else {
console.log('알 수 없는 오류가 발생했습니다', errors);
console.error('알 수 없는 오류가 발생했습니다', errors);
logout();
}

Expand Down
2 changes: 0 additions & 2 deletions src/components/common/PasswordEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ export default function PasswordEditSection() {
setErrors(newErrors);
return;
}

console.log('비밀번호 변경 요청:', { currentPw, newPw });
setIsEditing(false);
setErrors({});
};
Expand Down
1 change: 0 additions & 1 deletion src/components/dateCourse/dateCourse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function DateCourse({ defaultOpen = false }: { defaultOpen?: boolean }) {
} else {
setIsBookmarked(!isBookmarked);
}
// console.log('북마크 해제');
};

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/dateCourse/dateCourseSearchFilterOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function DateCourseSearchFilterOption({ options, type, value, onC
mode="search"
onSearchClick={handleSearch}
placeholder="ex: 서울시 강남구"
className="w-full"
className="!w-full min-w-full"
value={inputValue}
onChange={handleInputChange}
/>
Expand Down
1 change: 0 additions & 1 deletion src/components/modal/dateCourseSearchFilterModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export default function DateCourseSearchFilterModal({ onClose }: TDateCourseSear
const [errorMessages, setErrorMessages] = useState<string[]>(Array(7).fill(''));

const handleSearch = () => {
console.log('선택된 필터:', answers);
onClose();
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/settingTab/InfoSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function InfoSetting() {
value={nickname}
onChange={(e) => setNickname(e.target.value)}
onCancel={() => setNickname('')}
onSubmit={() => console.log('닉네임 저장:', nickname)}
onSubmit={() => {}}
/>
{/* 이메일 - 수정 불가능 */}
<EditableInputBox
Expand Down
44 changes: 34 additions & 10 deletions src/firebase/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// src/firebase/firebase.ts
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken } from 'firebase/messaging';
import type { Messaging } from 'firebase/messaging';
import { deleteToken, getMessaging, getToken, isSupported } from 'firebase/messaging';

const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
Expand All @@ -13,22 +14,34 @@ const firebaseConfig = {
};

const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
export let messaging: Messaging | null = null;
(async () => {
if (await isSupported()) {
messaging = getMessaging(app);
}
})();

export async function generateToken(): Promise<string | null> {
if (!(await isSupported())) return null;
if (!messaging) messaging = getMessaging(app);

// 권한 요청 (이미 허용/거부된 상태면 브라우저가 적절히 동작)
if ('Notification' in window && Notification.permission !== 'granted') {
const perm = await Notification.requestPermission();
if (perm !== 'granted') return null;
}

export const generateToken = async () => {
try {
const token = await getToken(messaging, {
vapidKey: import.meta.env.VITE_FIREBASE_VAPID_KEY,
serviceWorkerRegistration: await navigator.serviceWorker.getRegistration(),
});
if (!token) {
console.warn('FCM 토큰 생성에 실패했습니다. 알림 권한을 확인해주세요.');
}
return token;
} catch (error) {
console.error('FCM 토큰 생성 중 오류 발생:', error);
return token ?? null;
} catch (e) {
console.error('FCM getToken 실패:', e);
return null;
}
};
}

export const registerServiceWorker = async () => {
try {
Expand All @@ -39,3 +52,14 @@ export const registerServiceWorker = async () => {
console.error('Service Worker registration failed:', err);
}
};

export async function deleteFcmToken(): Promise<boolean> {
if (!(await isSupported())) return false;
if (!messaging) messaging = getMessaging(app);
try {
return await deleteToken(messaging);
} catch (e) {
console.error('FCM deleteToken 실패:', e);
return false;
}
}
39 changes: 0 additions & 39 deletions src/hooks/alarm/useDeviceToken.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/hooks/alarm/usePostDeviceToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useCoreMutation } from '../customQuery';

import { postDeviceToken } from '@/api/alarm/alarm';

export function useFirebase() {
const usePostDeviceToken = useCoreMutation(postDeviceToken);
return { usePostDeviceToken };
}
54 changes: 48 additions & 6 deletions src/hooks/customQuery.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { type MutationFunction, type QueryFunction, type QueryKey, useMutation, useQuery, type UseQueryResult } from '@tanstack/react-query';
import type { AxiosError } from 'axios';
import { toast } from 'sonner';

import type { TUseMutationCustomOptions, TUseQueryCustomOptions } from '@/types/common/common';

import { queryClient } from '@/api/queryClient';

export function useCoreQuery<TQueryFnData, TData = TQueryFnData>(
keyName: QueryKey,
query: QueryFunction<TQueryFnData, QueryKey>,
Expand All @@ -17,12 +18,53 @@ export function useCoreQuery<TQueryFnData, TData = TQueryFnData>(
});
}

export function useCoreMutation<T, U>(mutation: MutationFunction<T, U>, options?: TUseMutationCustomOptions) {
return useMutation({
export function useCoreMutation<TData, TVariables>(
mutation: MutationFunction<TData, TVariables>,
options?: TUseMutationCustomOptions<TData, TVariables, AxiosError<{ message?: string }>, { prevData?: unknown }>,
) {
const qc = queryClient;

const {
optimisticUpdate,
invalidateKeys,
userOnError,
userOnSuccess,
...rest // retry, gcTime 등 표준 옵션
} = options ?? {};

return useMutation<TData, AxiosError<{ message?: string }>, TVariables, { prevData?: unknown }>({
mutationFn: mutation,
onError: (error) => {
toast.error(error.response?.data.message || 'An error occurred.');

onMutate: async (vars) => {
if (!optimisticUpdate) return {};
await qc.cancelQueries({ queryKey: optimisticUpdate.key });
const prevData = qc.getQueryData(optimisticUpdate.key);
qc.setQueryData(optimisticUpdate.key, (old: any) => optimisticUpdate.updateFn(old, vars));
return { prevData };
},
...options,

onError: (error, vars, ctx) => {
// 롤백
if (optimisticUpdate && ctx?.prevData !== undefined) {
qc.setQueryData(optimisticUpdate.key, ctx.prevData);
}

// 사용자 콜백 위임
userOnError?.(error, vars, ctx);
},

onSuccess: async (data, vars, ctx) => {
// invalidate
if (invalidateKeys?.length) {
for (const key of invalidateKeys) {
await qc.invalidateQueries({ queryKey: key });
}
}
// 사용자 콜백 위임
userOnSuccess?.(data, vars, ctx);
},

// 나머지 표준 옵션 주입
...rest,
});
}
6 changes: 2 additions & 4 deletions src/pages/TestInputPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ export default function TestInputPage() {
placeholder="검색어 입력를 입력하세요"
value={search}
onChange={(e) => setSearch(e.target.value)} // 입력 상태 저장
onSearchClick={() => {
console.log('검색 실행:', search); // 검색 아이콘 클릭 시 예시
}}
onSearchClick={() => {}}
/>
</div>

Expand All @@ -38,7 +36,7 @@ export default function TestInputPage() {
value={nickname}
onChange={(e) => setNickname(e.target.value)}
onCancel={() => setNickname('')}
onSubmit={() => console.log('닉네임 저장:', nickname)}
onSubmit={() => {}}
/>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/auth/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function Login() {
navigate('/home');
},
onError: (err) => {
console.log(err.response?.data.message);
console.error(err.response?.data.message);
setError('잘못된 정보를 입력하였습니다.');
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/pages/auth/UserSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function User() {
navigate('/home');
},
onError: (err) => {
console.log(err);
console.error(err);
setError(err.response?.data.message!);
},
},
Expand Down
19 changes: 17 additions & 2 deletions src/pages/home/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useDeviceToken } from '@/hooks/alarm/useDeviceToken';
import { useEffect } from 'react';

import Banner from '@/components/home/banner';
import DateCourseStore from '@/components/home/dateCourseStore';
Expand All @@ -9,8 +9,23 @@ import MainInfo from '@/components/home/info';
import Level from '@/components/home/level';
import WordCloudCard from '@/components/home/wordCloud';

import { useDeviceTokenContext } from '@/providers/deviceTokenProvider';

function Home() {
useDeviceToken();
const { requestAndRegister } = useDeviceTokenContext();

useEffect(() => {
const fire = () => {
requestAndRegister().catch((err) => {
console.error('Device token 등록 실패:', err);
});
};

window.addEventListener('pointerdown', fire, { once: true });
return () => {
window.removeEventListener('pointerdown', fire);
};
}, [requestAndRegister]);

return (
<div className="bg-default-gray-100 min-h-screen mb-[40px]">
Expand Down
6 changes: 2 additions & 4 deletions src/pages/notice/Notice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,13 @@ export default function Notice() {
size: itemsPerPage,
});

console.log('API 응답:', response);

// 공지 목록과 페이지 수 설정 (빈 배열도 허용)
setNoticeList(response.result.noticeList ?? []);
setTotalPages(response.result.totalPages ?? 1);
} catch (err) {
// 오류 처리
setError('공지사항을 불러오는 데 실패했습니다.');
console.log(err);
console.error(err);
} finally {
setLoading(false);
}
Expand All @@ -66,7 +64,7 @@ export default function Notice() {
mode="search"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearchClick={() => console.log('검색 실행:', searchValue)}
onSearchClick={() => {}}
placeholder="찾으시는 내용을 입력해주세요."
className="mb-8"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/question/Question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function Question() {
setSearchValue(e.target.value);
setCurrentPage(1);
}}
onSearchClick={() => console.log('검색 실행:', searchValue)}
onSearchClick={() => {}}
placeholder="내용을 검색하세요"
className="mb-8"
/>
Expand Down
Loading