From 11a3d3dff8affecab0b20df87428dd2a0385f170 Mon Sep 17 00:00:00 2001 From: "bruno-sync-app[bot]" Date: Sun, 28 Dec 2025 07:43:12 +0000 Subject: [PATCH 01/11] chore: sync API from Bruno --- src/apis/Admin/api.ts | 174 +++++++++++++ src/apis/Admin/getGpaList.ts | 13 + src/apis/Admin/getLanguageTestList.ts | 13 + src/apis/Admin/index.ts | 5 + src/apis/Admin/putVerifyGpa.ts | 11 + src/apis/Admin/putVerifyLanguageTest.ts | 11 + src/apis/Auth/api.ts | 112 +++++++++ src/apis/Auth/deleteAccount.ts | 11 + src/apis/Auth/index.ts | 9 + src/apis/Auth/postAppleAuth.ts | 11 + src/apis/Auth/postEmailLogin.ts | 11 + src/apis/Auth/postEmailVerification.ts | 11 + src/apis/Auth/postKakaoAuth.ts | 11 + src/apis/Auth/postRefreshToken.ts | 11 + src/apis/Auth/postSignOut.ts | 11 + src/apis/Auth/postSignUp.ts | 11 + src/apis/MyPage/api.ts | 56 +++++ src/apis/MyPage/getProfile.ts | 13 + src/apis/MyPage/index.ts | 5 + .../MyPage/patchInterestedRegionCountry.ts | 11 + src/apis/MyPage/patchPassword.ts | 11 + src/apis/MyPage/patchProfile.ts | 11 + src/apis/Scores/api.ts | 78 ++++++ src/apis/Scores/getGpaList.ts | 13 + src/apis/Scores/getLanguageTestList.ts | 13 + src/apis/Scores/index.ts | 5 + src/apis/Scores/postCreateGpa.ts | 11 + src/apis/Scores/postCreateLanguageTest.ts | 11 + src/apis/applications/api.ts | 147 +++++++++++ src/apis/applications/getApplicants.ts | 13 + src/apis/applications/getCompetitors.ts | 13 + src/apis/applications/index.ts | 4 + .../applications/postSubmitApplication.ts | 11 + src/apis/chat/api.ts | 65 +++++ src/apis/chat/getChatMessages.ts | 14 ++ src/apis/chat/getChatPartner.ts | 14 ++ src/apis/chat/getChatRooms.ts | 13 + src/apis/chat/index.ts | 5 + src/apis/chat/putReadChatRoom.ts | 11 + src/apis/community/api.ts | 197 +++++++++++++++ src/apis/community/deleteComment.ts | 11 + src/apis/community/deleteLikePost.ts | 11 + src/apis/community/deletePost.ts | 11 + src/apis/community/getBoard.ts | 14 ++ src/apis/community/getBoardList.ts | 13 + src/apis/community/getPostDetail.ts | 14 ++ src/apis/community/index.ts | 12 + src/apis/community/patchUpdateComment.ts | 11 + src/apis/community/patchUpdatePost.ts | 11 + src/apis/community/postCreateComment.ts | 11 + src/apis/community/postCreatePost.ts | 11 + src/apis/community/postLikePost.ts | 11 + src/apis/image-upload/api.ts | 67 +++++ src/apis/image-upload/index.ts | 6 + .../image-upload/postSlackNotification.ts | 11 + src/apis/image-upload/postUploadGpaReport.ts | 11 + .../postUploadLanguageTestReport.ts | 11 + .../image-upload/postUploadProfileImage.ts | 11 + .../postUploadProfileImageBeforeSignup.ts | 11 + src/apis/kakao-api/api.ts | 33 +++ src/apis/kakao-api/getKakaoInfo.ts | 13 + src/apis/kakao-api/getKakaoUserIds.ts | 13 + src/apis/kakao-api/index.ts | 4 + src/apis/kakao-api/postKakaoUnlink.ts | 11 + src/apis/mentor/api.ts | 230 ++++++++++++++++++ src/apis/mentor/getAppliedMentorings.ts | 14 ++ src/apis/mentor/getMatchedMentors.ts | 14 ++ src/apis/mentor/getMentorDetail.ts | 14 ++ src/apis/mentor/getMentorList.ts | 14 ++ src/apis/mentor/getMyMentorPage.ts | 13 + src/apis/mentor/getReceivedMentorings.ts | 13 + .../mentor/getUnconfirmedMentoringCount.ts | 13 + src/apis/mentor/index.ts | 12 + .../mentor/legacy/patchConfirmMentoring.ts | 11 + src/apis/mentor/patchConfirmMentoring.ts | 11 + src/apis/mentor/patchMentoringStatus.ts | 11 + src/apis/mentor/postApplyMentoring.ts | 11 + src/apis/mentor/putUpdateMyMentorPage.ts | 11 + src/apis/news/api.ts | 79 ++++++ src/apis/news/deleteLikeNews.ts | 11 + src/apis/news/deleteNews.ts | 11 + src/apis/news/getNewsList.ts | 13 + src/apis/news/index.ts | 7 + src/apis/news/postCreateNews.ts | 11 + src/apis/news/postLikeNews.ts | 11 + src/apis/news/putUpdateNews.ts | 11 + src/apis/queryKeys.ts | 132 ++++++++++ src/apis/reports/api.ts | 15 ++ src/apis/reports/index.ts | 2 + src/apis/reports/postReport.ts | 11 + src/apis/universities/api.ts | 196 +++++++++++++++ src/apis/universities/deleteWish.ts | 11 + src/apis/universities/getByRegionCountry.ts | 13 + src/apis/universities/getIsWish.ts | 14 ++ .../getRecommendedUniversities.ts | 13 + src/apis/universities/getSearchFilter.ts | 13 + src/apis/universities/getSearchText.ts | 13 + src/apis/universities/getUniversityDetail.ts | 14 ++ src/apis/universities/getWishList.ts | 13 + src/apis/universities/index.ts | 10 + src/apis/universities/postAddWish.ts | 11 + src/apis/users/api.ts | 54 ++++ src/apis/users/deleteUnblockUser.ts | 11 + src/apis/users/getBlockedUsers.ts | 13 + src/apis/users/getNicknameExists.ts | 13 + src/apis/users/index.ts | 5 + src/apis/users/postBlockUser.ts | 11 + 107 files changed, 2658 insertions(+) create mode 100644 src/apis/Admin/api.ts create mode 100644 src/apis/Admin/getGpaList.ts create mode 100644 src/apis/Admin/getLanguageTestList.ts create mode 100644 src/apis/Admin/index.ts create mode 100644 src/apis/Admin/putVerifyGpa.ts create mode 100644 src/apis/Admin/putVerifyLanguageTest.ts create mode 100644 src/apis/Auth/api.ts create mode 100644 src/apis/Auth/deleteAccount.ts create mode 100644 src/apis/Auth/index.ts create mode 100644 src/apis/Auth/postAppleAuth.ts create mode 100644 src/apis/Auth/postEmailLogin.ts create mode 100644 src/apis/Auth/postEmailVerification.ts create mode 100644 src/apis/Auth/postKakaoAuth.ts create mode 100644 src/apis/Auth/postRefreshToken.ts create mode 100644 src/apis/Auth/postSignOut.ts create mode 100644 src/apis/Auth/postSignUp.ts create mode 100644 src/apis/MyPage/api.ts create mode 100644 src/apis/MyPage/getProfile.ts create mode 100644 src/apis/MyPage/index.ts create mode 100644 src/apis/MyPage/patchInterestedRegionCountry.ts create mode 100644 src/apis/MyPage/patchPassword.ts create mode 100644 src/apis/MyPage/patchProfile.ts create mode 100644 src/apis/Scores/api.ts create mode 100644 src/apis/Scores/getGpaList.ts create mode 100644 src/apis/Scores/getLanguageTestList.ts create mode 100644 src/apis/Scores/index.ts create mode 100644 src/apis/Scores/postCreateGpa.ts create mode 100644 src/apis/Scores/postCreateLanguageTest.ts create mode 100644 src/apis/applications/api.ts create mode 100644 src/apis/applications/getApplicants.ts create mode 100644 src/apis/applications/getCompetitors.ts create mode 100644 src/apis/applications/index.ts create mode 100644 src/apis/applications/postSubmitApplication.ts create mode 100644 src/apis/chat/api.ts create mode 100644 src/apis/chat/getChatMessages.ts create mode 100644 src/apis/chat/getChatPartner.ts create mode 100644 src/apis/chat/getChatRooms.ts create mode 100644 src/apis/chat/index.ts create mode 100644 src/apis/chat/putReadChatRoom.ts create mode 100644 src/apis/community/api.ts create mode 100644 src/apis/community/deleteComment.ts create mode 100644 src/apis/community/deleteLikePost.ts create mode 100644 src/apis/community/deletePost.ts create mode 100644 src/apis/community/getBoard.ts create mode 100644 src/apis/community/getBoardList.ts create mode 100644 src/apis/community/getPostDetail.ts create mode 100644 src/apis/community/index.ts create mode 100644 src/apis/community/patchUpdateComment.ts create mode 100644 src/apis/community/patchUpdatePost.ts create mode 100644 src/apis/community/postCreateComment.ts create mode 100644 src/apis/community/postCreatePost.ts create mode 100644 src/apis/community/postLikePost.ts create mode 100644 src/apis/image-upload/api.ts create mode 100644 src/apis/image-upload/index.ts create mode 100644 src/apis/image-upload/postSlackNotification.ts create mode 100644 src/apis/image-upload/postUploadGpaReport.ts create mode 100644 src/apis/image-upload/postUploadLanguageTestReport.ts create mode 100644 src/apis/image-upload/postUploadProfileImage.ts create mode 100644 src/apis/image-upload/postUploadProfileImageBeforeSignup.ts create mode 100644 src/apis/kakao-api/api.ts create mode 100644 src/apis/kakao-api/getKakaoInfo.ts create mode 100644 src/apis/kakao-api/getKakaoUserIds.ts create mode 100644 src/apis/kakao-api/index.ts create mode 100644 src/apis/kakao-api/postKakaoUnlink.ts create mode 100644 src/apis/mentor/api.ts create mode 100644 src/apis/mentor/getAppliedMentorings.ts create mode 100644 src/apis/mentor/getMatchedMentors.ts create mode 100644 src/apis/mentor/getMentorDetail.ts create mode 100644 src/apis/mentor/getMentorList.ts create mode 100644 src/apis/mentor/getMyMentorPage.ts create mode 100644 src/apis/mentor/getReceivedMentorings.ts create mode 100644 src/apis/mentor/getUnconfirmedMentoringCount.ts create mode 100644 src/apis/mentor/index.ts create mode 100644 src/apis/mentor/legacy/patchConfirmMentoring.ts create mode 100644 src/apis/mentor/patchConfirmMentoring.ts create mode 100644 src/apis/mentor/patchMentoringStatus.ts create mode 100644 src/apis/mentor/postApplyMentoring.ts create mode 100644 src/apis/mentor/putUpdateMyMentorPage.ts create mode 100644 src/apis/news/api.ts create mode 100644 src/apis/news/deleteLikeNews.ts create mode 100644 src/apis/news/deleteNews.ts create mode 100644 src/apis/news/getNewsList.ts create mode 100644 src/apis/news/index.ts create mode 100644 src/apis/news/postCreateNews.ts create mode 100644 src/apis/news/postLikeNews.ts create mode 100644 src/apis/news/putUpdateNews.ts create mode 100644 src/apis/queryKeys.ts create mode 100644 src/apis/reports/api.ts create mode 100644 src/apis/reports/index.ts create mode 100644 src/apis/reports/postReport.ts create mode 100644 src/apis/universities/api.ts create mode 100644 src/apis/universities/deleteWish.ts create mode 100644 src/apis/universities/getByRegionCountry.ts create mode 100644 src/apis/universities/getIsWish.ts create mode 100644 src/apis/universities/getRecommendedUniversities.ts create mode 100644 src/apis/universities/getSearchFilter.ts create mode 100644 src/apis/universities/getSearchText.ts create mode 100644 src/apis/universities/getUniversityDetail.ts create mode 100644 src/apis/universities/getWishList.ts create mode 100644 src/apis/universities/index.ts create mode 100644 src/apis/universities/postAddWish.ts create mode 100644 src/apis/users/api.ts create mode 100644 src/apis/users/deleteUnblockUser.ts create mode 100644 src/apis/users/getBlockedUsers.ts create mode 100644 src/apis/users/getNicknameExists.ts create mode 100644 src/apis/users/index.ts create mode 100644 src/apis/users/postBlockUser.ts diff --git a/src/apis/Admin/api.ts b/src/apis/Admin/api.ts new file mode 100644 index 00000000..48752a1d --- /dev/null +++ b/src/apis/Admin/api.ts @@ -0,0 +1,174 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface VerifyLanguageTestResponse { + id: number; + languageTestType: string; + languageTestScore: string; + verifyStatus: string; + rejectedReason: null; +} + +export type VerifyLanguageTestRequest = Record; + +export interface LanguageTestListResponseSort { + sorted: boolean; + unsorted: boolean; + empty: boolean; +} + +export interface LanguageTestListResponsePageable { + pageNumber: number; + pageSize: number; + sort: LanguageTestListResponsePageableSort; + offset: number; + paged: boolean; + unpaged: boolean; +} + +export interface LanguageTestListResponsePageableSort { + sorted: boolean; + unsorted: boolean; + empty: boolean; +} + +export interface LanguageTestListResponseContentItem { + languageTestScoreStatusResponse: LanguageTestListResponseContentItemLanguageTestScoreStatusResponse; + siteUserResponse: LanguageTestListResponseContentItemSiteUserResponse; +} + +export interface LanguageTestListResponseContentItemSiteUserResponse { + id: number; + nickname: string; + profileImageUrl: string; +} + +export interface LanguageTestListResponseContentItemLanguageTestScoreStatusResponse { + id: number; + languageTestResponse: LanguageTestListResponseContentItemLanguageTestScoreStatusResponseLanguageTestResponse; + verifyStatus: string; + rejectedReason: null; + createdAt: string; + updatedAt: string; +} + +export interface LanguageTestListResponseContentItemLanguageTestScoreStatusResponseLanguageTestResponse { + languageTestType: string; + languageTestScore: string; + languageTestReportUrl: string; +} + +export interface LanguageTestListResponse { + content: LanguageTestListResponseContentItem[]; + pageable: LanguageTestListResponsePageable; + totalElements: number; + totalPages: number; + last: boolean; + size: number; + number: number; + sort: LanguageTestListResponseSort; + numberOfElements: number; + first: boolean; + empty: boolean; +} + +export interface VerifyGpaResponse { + id: number; + gpa: number; + gpaCriteria: number; + verifyStatus: string; + rejectedReason: null; +} + +export type VerifyGpaRequest = Record; + +export interface GpaListResponseSort { + sorted: boolean; + unsorted: boolean; + empty: boolean; +} + +export interface GpaListResponsePageable { + pageNumber: number; + pageSize: number; + sort: GpaListResponsePageableSort; + offset: number; + paged: boolean; + unpaged: boolean; +} + +export interface GpaListResponsePageableSort { + sorted: boolean; + unsorted: boolean; + empty: boolean; +} + +export interface GpaListResponseContentItem { + gpaScoreStatusResponse: GpaListResponseContentItemGpaScoreStatusResponse; + siteUserResponse: GpaListResponseContentItemSiteUserResponse; +} + +export interface GpaListResponseContentItemSiteUserResponse { + id: number; + nickname: string; + profileImageUrl: string; +} + +export interface GpaListResponseContentItemGpaScoreStatusResponse { + id: number; + gpaResponse: GpaListResponseContentItemGpaScoreStatusResponseGpaResponse; + verifyStatus: string; + rejectedReason: null; + createdAt: string; + updatedAt: string; +} + +export interface GpaListResponseContentItemGpaScoreStatusResponseGpaResponse { + gpa: number; + gpaCriteria: number; + gpaReportUrl: string; +} + +export interface GpaListResponse { + content: GpaListResponseContentItem[]; + pageable: GpaListResponsePageable; + totalElements: number; + totalPages: number; + last: boolean; + size: number; + number: number; + sort: GpaListResponseSort; + numberOfElements: number; + first: boolean; + empty: boolean; +} + +export const adminApi = { + putVerifyLanguageTest: async (params: { languageTestScoreId: string | number, data?: VerifyLanguageTestRequest }): Promise => { + const res = await axiosInstance.put( + `/admin/scores/language-tests/${params.languageTestScoreId}`, params?.data + ); + return res.data; + }, + + getLanguageTestList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/admin/scores/language-tests?page=1&size=10`, { params: params?.params } + ); + return res.data; + }, + + putVerifyGpa: async (params: { gpaScoreId: string | number, data?: VerifyGpaRequest }): Promise => { + const res = await axiosInstance.put( + `/admin/scores/gpas/${params.gpaScoreId}`, params?.data + ); + return res.data; + }, + + getGpaList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/admin/scores/gpas`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/Admin/getGpaList.ts b/src/apis/Admin/getGpaList.ts new file mode 100644 index 00000000..d3ae89e6 --- /dev/null +++ b/src/apis/Admin/getGpaList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { adminApi, GpaListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetGpaList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.Admin.gpaList, params], + queryFn: () => adminApi.getGpaList(params ? { params } : {}), + }); +}; + +export default useGetGpaList; \ No newline at end of file diff --git a/src/apis/Admin/getLanguageTestList.ts b/src/apis/Admin/getLanguageTestList.ts new file mode 100644 index 00000000..6d9325d8 --- /dev/null +++ b/src/apis/Admin/getLanguageTestList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { adminApi, LanguageTestListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetLanguageTestList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.Admin.languageTestList, params], + queryFn: () => adminApi.getLanguageTestList(params ? { params } : {}), + }); +}; + +export default useGetLanguageTestList; \ No newline at end of file diff --git a/src/apis/Admin/index.ts b/src/apis/Admin/index.ts new file mode 100644 index 00000000..d5264145 --- /dev/null +++ b/src/apis/Admin/index.ts @@ -0,0 +1,5 @@ +export { adminApi } from './api'; +export { default as getGpaList } from './getGpaList'; +export { default as getLanguageTestList } from './getLanguageTestList'; +export { default as putVerifyGpa } from './putVerifyGpa'; +export { default as putVerifyLanguageTest } from './putVerifyLanguageTest'; diff --git a/src/apis/Admin/putVerifyGpa.ts b/src/apis/Admin/putVerifyGpa.ts new file mode 100644 index 00000000..8073d81a --- /dev/null +++ b/src/apis/Admin/putVerifyGpa.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { adminApi, VerifyGpaResponse, VerifyGpaRequest } from "./api"; + +const usePutVerifyGpa = () => { + return useMutation({ + mutationFn: (variables) => adminApi.putVerifyGpa(variables), + }); +}; + +export default usePutVerifyGpa; \ No newline at end of file diff --git a/src/apis/Admin/putVerifyLanguageTest.ts b/src/apis/Admin/putVerifyLanguageTest.ts new file mode 100644 index 00000000..fa37c5e4 --- /dev/null +++ b/src/apis/Admin/putVerifyLanguageTest.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { adminApi, VerifyLanguageTestResponse, VerifyLanguageTestRequest } from "./api"; + +const usePutVerifyLanguageTest = () => { + return useMutation({ + mutationFn: (variables) => adminApi.putVerifyLanguageTest(variables), + }); +}; + +export default usePutVerifyLanguageTest; \ No newline at end of file diff --git a/src/apis/Auth/api.ts b/src/apis/Auth/api.ts new file mode 100644 index 00000000..7d6ac3be --- /dev/null +++ b/src/apis/Auth/api.ts @@ -0,0 +1,112 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export type SignOutResponse = Record; + +export type SignOutRequest = Record; + +export interface AppleAuthResponse { + isRegistered: boolean; + nickname: null; + email: string; + profileImageUrl: null; + signUpToken: string; +} + +export type AppleAuthRequest = Record; + +export interface RefreshTokenResponse { + accessToken: string; +} + +export type RefreshTokenRequest = Record; + +export interface EmailLoginResponse { + accessToken: string; + refreshToken: string; +} + +export type EmailLoginRequest = Record; + +export interface EmailVerificationResponse { + signUpToken: string; +} + +export type EmailVerificationRequest = Record; + +export interface KakaoAuthResponse { + isRegistered: boolean; + nickname: string; + email: string; + profileImageUrl: string; + signUpToken: string; +} + +export type KakaoAuthRequest = Record; + +export type AccountResponse = void; + +export interface SignUpResponse { + accessToken: string; + refreshToken: string; +} + +export type SignUpRequest = Record; + +export const authApi = { + postSignOut: async (params: { data?: SignOutRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/sign-out`, params?.data + ); + return res.data; + }, + + postAppleAuth: async (params: { data?: AppleAuthRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/apple`, params?.data + ); + return res.data; + }, + + postRefreshToken: async (params: { data?: RefreshTokenRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/reissue`, params?.data + ); + return res.data; + }, + + postEmailLogin: async (params: { data?: EmailLoginRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/email/sign-in`, params?.data + ); + return res.data; + }, + + postEmailVerification: async (params: { data?: EmailVerificationRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/email/sign-up`, params?.data + ); + return res.data; + }, + + postKakaoAuth: async (params: { data?: KakaoAuthRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/kakao`, params?.data + ); + return res.data; + }, + + deleteAccount: async (): Promise => { + const res = await axiosInstance.delete( + `/auth/quit` + ); + return res.data; + }, + + postSignUp: async (params: { data?: SignUpRequest }): Promise => { + const res = await axiosInstance.post( + `/auth/sign-up`, params?.data + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/Auth/deleteAccount.ts b/src/apis/Auth/deleteAccount.ts new file mode 100644 index 00000000..00038562 --- /dev/null +++ b/src/apis/Auth/deleteAccount.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, AccountResponse, AccountRequest } from "./api"; + +const useDeleteAccount = () => { + return useMutation({ + mutationFn: (data) => authApi.deleteAccount({ data }), + }); +}; + +export default useDeleteAccount; \ No newline at end of file diff --git a/src/apis/Auth/index.ts b/src/apis/Auth/index.ts new file mode 100644 index 00000000..844947a4 --- /dev/null +++ b/src/apis/Auth/index.ts @@ -0,0 +1,9 @@ +export { authApi } from './api'; +export { default as deleteAccount } from './deleteAccount'; +export { default as postAppleAuth } from './postAppleAuth'; +export { default as postEmailLogin } from './postEmailLogin'; +export { default as postEmailVerification } from './postEmailVerification'; +export { default as postKakaoAuth } from './postKakaoAuth'; +export { default as postRefreshToken } from './postRefreshToken'; +export { default as postSignOut } from './postSignOut'; +export { default as postSignUp } from './postSignUp'; diff --git a/src/apis/Auth/postAppleAuth.ts b/src/apis/Auth/postAppleAuth.ts new file mode 100644 index 00000000..cb30b87b --- /dev/null +++ b/src/apis/Auth/postAppleAuth.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, AppleAuthResponse, AppleAuthRequest } from "./api"; + +const usePostAppleAuth = () => { + return useMutation({ + mutationFn: (data) => authApi.postAppleAuth({ data }), + }); +}; + +export default usePostAppleAuth; \ No newline at end of file diff --git a/src/apis/Auth/postEmailLogin.ts b/src/apis/Auth/postEmailLogin.ts new file mode 100644 index 00000000..d8ef06df --- /dev/null +++ b/src/apis/Auth/postEmailLogin.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, EmailLoginResponse, EmailLoginRequest } from "./api"; + +const usePostEmailLogin = () => { + return useMutation({ + mutationFn: (data) => authApi.postEmailLogin({ data }), + }); +}; + +export default usePostEmailLogin; \ No newline at end of file diff --git a/src/apis/Auth/postEmailVerification.ts b/src/apis/Auth/postEmailVerification.ts new file mode 100644 index 00000000..99aefafe --- /dev/null +++ b/src/apis/Auth/postEmailVerification.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, EmailVerificationResponse, EmailVerificationRequest } from "./api"; + +const usePostEmailVerification = () => { + return useMutation({ + mutationFn: (data) => authApi.postEmailVerification({ data }), + }); +}; + +export default usePostEmailVerification; \ No newline at end of file diff --git a/src/apis/Auth/postKakaoAuth.ts b/src/apis/Auth/postKakaoAuth.ts new file mode 100644 index 00000000..2aaebd97 --- /dev/null +++ b/src/apis/Auth/postKakaoAuth.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, KakaoAuthResponse, KakaoAuthRequest } from "./api"; + +const usePostKakaoAuth = () => { + return useMutation({ + mutationFn: (data) => authApi.postKakaoAuth({ data }), + }); +}; + +export default usePostKakaoAuth; \ No newline at end of file diff --git a/src/apis/Auth/postRefreshToken.ts b/src/apis/Auth/postRefreshToken.ts new file mode 100644 index 00000000..ae86a780 --- /dev/null +++ b/src/apis/Auth/postRefreshToken.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, RefreshTokenResponse, RefreshTokenRequest } from "./api"; + +const usePostRefreshToken = () => { + return useMutation({ + mutationFn: (data) => authApi.postRefreshToken({ data }), + }); +}; + +export default usePostRefreshToken; \ No newline at end of file diff --git a/src/apis/Auth/postSignOut.ts b/src/apis/Auth/postSignOut.ts new file mode 100644 index 00000000..8d31dc93 --- /dev/null +++ b/src/apis/Auth/postSignOut.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, SignOutResponse, SignOutRequest } from "./api"; + +const usePostSignOut = () => { + return useMutation({ + mutationFn: (data) => authApi.postSignOut({ data }), + }); +}; + +export default usePostSignOut; \ No newline at end of file diff --git a/src/apis/Auth/postSignUp.ts b/src/apis/Auth/postSignUp.ts new file mode 100644 index 00000000..eadc9cb8 --- /dev/null +++ b/src/apis/Auth/postSignUp.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { authApi, SignUpResponse, SignUpRequest } from "./api"; + +const usePostSignUp = () => { + return useMutation({ + mutationFn: (data) => authApi.postSignUp({ data }), + }); +}; + +export default usePostSignUp; \ No newline at end of file diff --git a/src/apis/MyPage/api.ts b/src/apis/MyPage/api.ts new file mode 100644 index 00000000..d6f5f956 --- /dev/null +++ b/src/apis/MyPage/api.ts @@ -0,0 +1,56 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export type InterestedRegionCountryResponse = void; + +export type InterestedRegionCountryRequest = Record; + +export type ProfileResponse = Record; + +export type ProfileRequest = Record; + +export interface ProfileResponse { + likedUniversityCount: number; + nickname: string; + profileImageUrl: string; + role: string; + authType: string; + email: string; + likedPostCount: number; + likedMentorCount: number; + interestedCountries: string[]; +} + +export type PasswordResponse = void; + +export type PasswordRequest = Record; + +export const myPageApi = { + patchInterestedRegionCountry: async (params: { data?: InterestedRegionCountryRequest }): Promise => { + const res = await axiosInstance.patch( + `/my/interested-location`, params?.data + ); + return res.data; + }, + + patchProfile: async (params: { data?: ProfileRequest }): Promise => { + const res = await axiosInstance.patch( + `/my`, params?.data + ); + return res.data; + }, + + getProfile: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/my`, { params: params?.params } + ); + return res.data; + }, + + patchPassword: async (params: { data?: PasswordRequest }): Promise => { + const res = await axiosInstance.patch( + `/my/password`, params?.data + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/MyPage/getProfile.ts b/src/apis/MyPage/getProfile.ts new file mode 100644 index 00000000..042c0767 --- /dev/null +++ b/src/apis/MyPage/getProfile.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { myPageApi, ProfileResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetProfile = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.MyPage.profile, params], + queryFn: () => myPageApi.getProfile(params ? { params } : {}), + }); +}; + +export default useGetProfile; \ No newline at end of file diff --git a/src/apis/MyPage/index.ts b/src/apis/MyPage/index.ts new file mode 100644 index 00000000..31a7cccf --- /dev/null +++ b/src/apis/MyPage/index.ts @@ -0,0 +1,5 @@ +export { myPageApi } from './api'; +export { default as getProfile } from './getProfile'; +export { default as patchInterestedRegionCountry } from './patchInterestedRegionCountry'; +export { default as patchPassword } from './patchPassword'; +export { default as patchProfile } from './patchProfile'; diff --git a/src/apis/MyPage/patchInterestedRegionCountry.ts b/src/apis/MyPage/patchInterestedRegionCountry.ts new file mode 100644 index 00000000..0ea8b18f --- /dev/null +++ b/src/apis/MyPage/patchInterestedRegionCountry.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { myPageApi, InterestedRegionCountryResponse, InterestedRegionCountryRequest } from "./api"; + +const usePatchInterestedRegionCountry = () => { + return useMutation({ + mutationFn: (data) => myPageApi.patchInterestedRegionCountry({ data }), + }); +}; + +export default usePatchInterestedRegionCountry; \ No newline at end of file diff --git a/src/apis/MyPage/patchPassword.ts b/src/apis/MyPage/patchPassword.ts new file mode 100644 index 00000000..8592b77e --- /dev/null +++ b/src/apis/MyPage/patchPassword.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { myPageApi, PasswordResponse, PasswordRequest } from "./api"; + +const usePatchPassword = () => { + return useMutation({ + mutationFn: (data) => myPageApi.patchPassword({ data }), + }); +}; + +export default usePatchPassword; \ No newline at end of file diff --git a/src/apis/MyPage/patchProfile.ts b/src/apis/MyPage/patchProfile.ts new file mode 100644 index 00000000..20204ad8 --- /dev/null +++ b/src/apis/MyPage/patchProfile.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { myPageApi, ProfileResponse, ProfileRequest } from "./api"; + +const usePatchProfile = () => { + return useMutation({ + mutationFn: (data) => myPageApi.patchProfile({ data }), + }); +}; + +export default usePatchProfile; \ No newline at end of file diff --git a/src/apis/Scores/api.ts b/src/apis/Scores/api.ts new file mode 100644 index 00000000..5b45182c --- /dev/null +++ b/src/apis/Scores/api.ts @@ -0,0 +1,78 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface CreateLanguageTestResponse { + id: number; +} + +export type CreateLanguageTestRequest = Record; + +export interface LanguageTestListResponseLanguageTestScoreStatusResponseListItem { + id: number; + languageTestResponse: LanguageTestListResponseLanguageTestScoreStatusResponseListItemLanguageTestResponse; + verifyStatus: string; + rejectedReason: null; +} + +export interface LanguageTestListResponseLanguageTestScoreStatusResponseListItemLanguageTestResponse { + languageTestType: string; + languageTestScore: string; + languageTestReportUrl: string; +} + +export interface LanguageTestListResponse { + languageTestScoreStatusResponseList: LanguageTestListResponseLanguageTestScoreStatusResponseListItem[]; +} + +export interface CreateGpaResponse { + id: number; +} + +export type CreateGpaRequest = Record; + +export interface GpaListResponseGpaScoreStatusResponseListItem { + id: number; + gpaResponse: GpaListResponseGpaScoreStatusResponseListItemGpaResponse; + verifyStatus: string; + rejectedReason: null; +} + +export interface GpaListResponseGpaScoreStatusResponseListItemGpaResponse { + gpa: number; + gpaCriteria: number; + gpaReportUrl: string; +} + +export interface GpaListResponse { + gpaScoreStatusResponseList: GpaListResponseGpaScoreStatusResponseListItem[]; +} + +export const scoresApi = { + postCreateLanguageTest: async (params: { data?: CreateLanguageTestRequest }): Promise => { + const res = await axiosInstance.post( + `/scores/language-tests`, params?.data + ); + return res.data; + }, + + getLanguageTestList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/scores/language-tests`, { params: params?.params } + ); + return res.data; + }, + + postCreateGpa: async (params: { data?: CreateGpaRequest }): Promise => { + const res = await axiosInstance.post( + `/scores/gpas`, params?.data + ); + return res.data; + }, + + getGpaList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/scores/gpas`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/Scores/getGpaList.ts b/src/apis/Scores/getGpaList.ts new file mode 100644 index 00000000..b402a643 --- /dev/null +++ b/src/apis/Scores/getGpaList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { scoresApi, GpaListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetGpaList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.Scores.gpaList, params], + queryFn: () => scoresApi.getGpaList(params ? { params } : {}), + }); +}; + +export default useGetGpaList; \ No newline at end of file diff --git a/src/apis/Scores/getLanguageTestList.ts b/src/apis/Scores/getLanguageTestList.ts new file mode 100644 index 00000000..ace30a13 --- /dev/null +++ b/src/apis/Scores/getLanguageTestList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { scoresApi, LanguageTestListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetLanguageTestList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.Scores.languageTestList, params], + queryFn: () => scoresApi.getLanguageTestList(params ? { params } : {}), + }); +}; + +export default useGetLanguageTestList; \ No newline at end of file diff --git a/src/apis/Scores/index.ts b/src/apis/Scores/index.ts new file mode 100644 index 00000000..73669f35 --- /dev/null +++ b/src/apis/Scores/index.ts @@ -0,0 +1,5 @@ +export { scoresApi } from './api'; +export { default as getGpaList } from './getGpaList'; +export { default as getLanguageTestList } from './getLanguageTestList'; +export { default as postCreateGpa } from './postCreateGpa'; +export { default as postCreateLanguageTest } from './postCreateLanguageTest'; diff --git a/src/apis/Scores/postCreateGpa.ts b/src/apis/Scores/postCreateGpa.ts new file mode 100644 index 00000000..6255a5d0 --- /dev/null +++ b/src/apis/Scores/postCreateGpa.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { scoresApi, CreateGpaResponse, CreateGpaRequest } from "./api"; + +const usePostCreateGpa = () => { + return useMutation({ + mutationFn: (data) => scoresApi.postCreateGpa({ data }), + }); +}; + +export default usePostCreateGpa; \ No newline at end of file diff --git a/src/apis/Scores/postCreateLanguageTest.ts b/src/apis/Scores/postCreateLanguageTest.ts new file mode 100644 index 00000000..b6201433 --- /dev/null +++ b/src/apis/Scores/postCreateLanguageTest.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { scoresApi, CreateLanguageTestResponse, CreateLanguageTestRequest } from "./api"; + +const usePostCreateLanguageTest = () => { + return useMutation({ + mutationFn: (data) => scoresApi.postCreateLanguageTest({ data }), + }); +}; + +export default usePostCreateLanguageTest; \ No newline at end of file diff --git a/src/apis/applications/api.ts b/src/apis/applications/api.ts new file mode 100644 index 00000000..f4879dd1 --- /dev/null +++ b/src/apis/applications/api.ts @@ -0,0 +1,147 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface CompetitorsResponseThirdChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: CompetitorsResponseThirdChoiceItemApplicantsItem[]; +} + +export interface CompetitorsResponseThirdChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface CompetitorsResponseSecondChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: CompetitorsResponseSecondChoiceItemApplicantsItem[]; +} + +export interface CompetitorsResponseSecondChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface CompetitorsResponseFirstChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: CompetitorsResponseFirstChoiceItemApplicantsItem[]; +} + +export interface CompetitorsResponseFirstChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface CompetitorsResponse { + firstChoice: CompetitorsResponseFirstChoiceItem[]; + secondChoice: CompetitorsResponseSecondChoiceItem[]; + thirdChoice: CompetitorsResponseThirdChoiceItem[]; +} + +export interface SubmitApplicationResponseAppliedUniversities { + firstChoiceUniversity: string; + secondChoiceUniversity: string; + thirdChoiceUniversity: string; +} + +export interface SubmitApplicationResponse { + totalApplyCount: number; + applyCount: number; + appliedUniversities: SubmitApplicationResponseAppliedUniversities; +} + +export type SubmitApplicationRequest = Record; + +export interface ApplicantsResponseThirdChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: ApplicantsResponseThirdChoiceItemApplicantsItem[]; +} + +export interface ApplicantsResponseThirdChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface ApplicantsResponseSecondChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: ApplicantsResponseSecondChoiceItemApplicantsItem[]; +} + +export interface ApplicantsResponseSecondChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface ApplicantsResponseFirstChoiceItem { + koreanName: string; + studentCapacity: number; + region: string; + country: string; + applicants: ApplicantsResponseFirstChoiceItemApplicantsItem[]; +} + +export interface ApplicantsResponseFirstChoiceItemApplicantsItem { + nicknameForApply: string; + gpa: number; + testType: string; + testScore: string; + isMine: boolean; +} + +export interface ApplicantsResponse { + firstChoice: ApplicantsResponseFirstChoiceItem[]; + secondChoice: ApplicantsResponseSecondChoiceItem[]; + thirdChoice: ApplicantsResponseThirdChoiceItem[]; +} + +export const applicationsApi = { + getCompetitors: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/applications/competitors`, { params: params?.params } + ); + return res.data; + }, + + postSubmitApplication: async (params: { data?: SubmitApplicationRequest }): Promise => { + const res = await axiosInstance.post( + `/applications`, params?.data + ); + return res.data; + }, + + getApplicants: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/applications`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/applications/getApplicants.ts b/src/apis/applications/getApplicants.ts new file mode 100644 index 00000000..2a266ec3 --- /dev/null +++ b/src/apis/applications/getApplicants.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { applicationsApi, ApplicantsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetApplicants = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.applications.applicants, params], + queryFn: () => applicationsApi.getApplicants(params ? { params } : {}), + }); +}; + +export default useGetApplicants; \ No newline at end of file diff --git a/src/apis/applications/getCompetitors.ts b/src/apis/applications/getCompetitors.ts new file mode 100644 index 00000000..4d3ccbc9 --- /dev/null +++ b/src/apis/applications/getCompetitors.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { applicationsApi, CompetitorsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetCompetitors = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.applications.competitors, params], + queryFn: () => applicationsApi.getCompetitors(params ? { params } : {}), + }); +}; + +export default useGetCompetitors; \ No newline at end of file diff --git a/src/apis/applications/index.ts b/src/apis/applications/index.ts new file mode 100644 index 00000000..3169f9b1 --- /dev/null +++ b/src/apis/applications/index.ts @@ -0,0 +1,4 @@ +export { applicationsApi } from './api'; +export { default as getApplicants } from './getApplicants'; +export { default as getCompetitors } from './getCompetitors'; +export { default as postSubmitApplication } from './postSubmitApplication'; diff --git a/src/apis/applications/postSubmitApplication.ts b/src/apis/applications/postSubmitApplication.ts new file mode 100644 index 00000000..5434d6ee --- /dev/null +++ b/src/apis/applications/postSubmitApplication.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { applicationsApi, SubmitApplicationResponse, SubmitApplicationRequest } from "./api"; + +const usePostSubmitApplication = () => { + return useMutation({ + mutationFn: (data) => applicationsApi.postSubmitApplication({ data }), + }); +}; + +export default usePostSubmitApplication; \ No newline at end of file diff --git a/src/apis/chat/api.ts b/src/apis/chat/api.ts new file mode 100644 index 00000000..3041ff33 --- /dev/null +++ b/src/apis/chat/api.ts @@ -0,0 +1,65 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface ChatMessagesResponseContentItem { + id: number; + content: string; + senderId: number; + createdAt: string; + attachments: ChatMessagesResponseContentItemAttachmentsItem[]; +} + +export interface ChatMessagesResponseContentItemAttachmentsItem { + id: number; + isImage: boolean; + url: string; + thumbnailUrl: string | null; + createdAt: string; +} + +export interface ChatMessagesResponse { + nextPageNumber: number; + content: ChatMessagesResponseContentItem[]; +} + +export type ChatRoomsResponse = void; + +export type ReadChatRoomResponse = void; + +export type ReadChatRoomRequest = Record; + +export interface ChatPartnerResponse { + partnerId: number; + nickname: string; + profileUrl: string; +} + +export const chatApi = { + getChatMessages: async (params: { roomId: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/chats/rooms/${params.roomId}?size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } + ); + return res.data; + }, + + getChatRooms: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/chats/rooms`, { params: params?.params } + ); + return res.data; + }, + + putReadChatRoom: async (params: { roomId: string | number, data?: ReadChatRoomRequest }): Promise => { + const res = await axiosInstance.put( + `//chats/rooms/${params.roomId}/read`, params?.data + ); + return res.data; + }, + + getChatPartner: async (params: { roomId: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/chats/rooms/${params.roomId}/partner`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/chat/getChatMessages.ts b/src/apis/chat/getChatMessages.ts new file mode 100644 index 00000000..2cf05b68 --- /dev/null +++ b/src/apis/chat/getChatMessages.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { chatApi, ChatMessagesResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetChatMessages = (roomId: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.chat.chatMessages, roomId, defaultSize, defaultPage, params], + queryFn: () => chatApi.getChatMessages({ roomId, defaultSize, defaultPage, params }), + enabled: !!roomId && !!defaultSize && !!defaultPage, + }); +}; + +export default useGetChatMessages; \ No newline at end of file diff --git a/src/apis/chat/getChatPartner.ts b/src/apis/chat/getChatPartner.ts new file mode 100644 index 00000000..ab3ab0c0 --- /dev/null +++ b/src/apis/chat/getChatPartner.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { chatApi, ChatPartnerResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetChatPartner = (roomId: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.chat.chatPartner, roomId, params], + queryFn: () => chatApi.getChatPartner({ roomId, params }), + enabled: !!roomId, + }); +}; + +export default useGetChatPartner; \ No newline at end of file diff --git a/src/apis/chat/getChatRooms.ts b/src/apis/chat/getChatRooms.ts new file mode 100644 index 00000000..b2c6c0c4 --- /dev/null +++ b/src/apis/chat/getChatRooms.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { chatApi, ChatRoomsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetChatRooms = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.chat.chatRooms, params], + queryFn: () => chatApi.getChatRooms(params ? { params } : {}), + }); +}; + +export default useGetChatRooms; \ No newline at end of file diff --git a/src/apis/chat/index.ts b/src/apis/chat/index.ts new file mode 100644 index 00000000..c6566109 --- /dev/null +++ b/src/apis/chat/index.ts @@ -0,0 +1,5 @@ +export { chatApi } from './api'; +export { default as getChatMessages } from './getChatMessages'; +export { default as getChatPartner } from './getChatPartner'; +export { default as getChatRooms } from './getChatRooms'; +export { default as putReadChatRoom } from './putReadChatRoom'; diff --git a/src/apis/chat/putReadChatRoom.ts b/src/apis/chat/putReadChatRoom.ts new file mode 100644 index 00000000..02af78df --- /dev/null +++ b/src/apis/chat/putReadChatRoom.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { chatApi, ReadChatRoomResponse, ReadChatRoomRequest } from "./api"; + +const usePutReadChatRoom = () => { + return useMutation({ + mutationFn: (variables) => chatApi.putReadChatRoom(variables), + }); +}; + +export default usePutReadChatRoom; \ No newline at end of file diff --git a/src/apis/community/api.ts b/src/apis/community/api.ts new file mode 100644 index 00000000..52eaec57 --- /dev/null +++ b/src/apis/community/api.ts @@ -0,0 +1,197 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface BoardListResponse { + 0: string; + 1: string; + 2: string; + 3: string; +} + +export interface BoardResponseItem { + id: number; + title: string; + content: string; + likeCount: number; + commentCount: number; + createdAt: string; + updatedAt: string; + postCategory: string; + postThumbnailUrl: null | string; +} + +export interface BoardResponse { + 0: BoardResponse0; + 1: BoardResponse1; + 2: BoardResponse2; + 3: BoardResponse3; +} + +export interface CommentResponse { + id: number; +} + +export interface UpdateCommentResponse { + id: number; +} + +export type UpdateCommentRequest = Record; + +export interface CreateCommentResponse { + id: number; +} + +export type CreateCommentRequest = Record; + +export interface PostResponse { + id: number; +} + +export interface UpdatePostResponse { + id: number; +} + +export type UpdatePostRequest = Record; + +export interface CreatePostResponse { + id: number; +} + +export type CreatePostRequest = Record; + +export interface PostDetailResponsePostFindPostImageResponsesItem { + id: number; + imageUrl: string; +} + +export interface PostDetailResponsePostFindCommentResponsesItem { + id: number; + parentId: null | number; + content: string; + isOwner: boolean; + createdAt: string; + updatedAt: string; + postFindSiteUserResponse: PostDetailResponsePostFindCommentResponsesItemPostFindSiteUserResponse; +} + +export interface PostDetailResponsePostFindCommentResponsesItemPostFindSiteUserResponse { + id: number; + nickname: string; + profileImageUrl: string; +} + +export interface PostDetailResponsePostFindSiteUserResponse { + id: number; + nickname: string; + profileImageUrl: string; +} + +export interface PostDetailResponsePostFindBoardResponse { + code: string; + koreanName: string; +} + +export interface PostDetailResponse { + id: number; + title: string; + content: string; + isQuestion: boolean; + likeCount: number; + viewCount: number; + commentCount: number; + postCategory: string; + isOwner: boolean; + isLiked: boolean; + createdAt: string; + updatedAt: string; + postFindBoardResponse: PostDetailResponsePostFindBoardResponse; + postFindSiteUserResponse: PostDetailResponsePostFindSiteUserResponse; + postFindCommentResponses: PostDetailResponsePostFindCommentResponsesItem[]; + postFindPostImageResponses: PostDetailResponsePostFindPostImageResponsesItem[]; +} + +export interface LikePostResponse { + likeCount: number; + isLiked: boolean; +} + +export type LikePostRequest = Record; + +export const communityApi = { + getBoardList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/boards`, { params: params?.params } + ); + return res.data; + }, + + getBoard: async (params: { boardCode: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/boards/${params.boardCode}`, { params: params?.params } + ); + return res.data; + }, + + deleteComment: async (params: { commentId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/comments/${params.commentId}` + ); + return res.data; + }, + + patchUpdateComment: async (params: { commentId: string | number, data?: UpdateCommentRequest }): Promise => { + const res = await axiosInstance.patch( + `/comments/${params.commentId}`, params?.data + ); + return res.data; + }, + + postCreateComment: async (params: { data?: CreateCommentRequest }): Promise => { + const res = await axiosInstance.post( + `/comments`, params?.data + ); + return res.data; + }, + + deletePost: async (params: { postId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/posts/${params.postId}` + ); + return res.data; + }, + + patchUpdatePost: async (params: { postId: string | number, data?: UpdatePostRequest }): Promise => { + const res = await axiosInstance.patch( + `/posts/${params.postId}`, params?.data + ); + return res.data; + }, + + postCreatePost: async (params: { data?: CreatePostRequest }): Promise => { + const res = await axiosInstance.post( + `/posts`, params?.data + ); + return res.data; + }, + + getPostDetail: async (params: { postId: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/posts/${params.postId}`, { params: params?.params } + ); + return res.data; + }, + + postLikePost: async (params: { postId: string | number, data?: LikePostRequest }): Promise => { + const res = await axiosInstance.post( + `/posts/${params.postId}/like`, params?.data + ); + return res.data; + }, + + deleteLikePost: async (params: { postId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/posts/${params.postId}/like` + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/community/deleteComment.ts b/src/apis/community/deleteComment.ts new file mode 100644 index 00000000..6bea8c7e --- /dev/null +++ b/src/apis/community/deleteComment.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, CommentResponse, CommentRequest } from "./api"; + +const useDeleteComment = () => { + return useMutation({ + mutationFn: (variables) => communityApi.deleteComment(variables), + }); +}; + +export default useDeleteComment; \ No newline at end of file diff --git a/src/apis/community/deleteLikePost.ts b/src/apis/community/deleteLikePost.ts new file mode 100644 index 00000000..63036f50 --- /dev/null +++ b/src/apis/community/deleteLikePost.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, LikePostResponse, LikePostRequest } from "./api"; + +const useDeleteLikePost = () => { + return useMutation({ + mutationFn: (variables) => communityApi.deleteLikePost(variables), + }); +}; + +export default useDeleteLikePost; \ No newline at end of file diff --git a/src/apis/community/deletePost.ts b/src/apis/community/deletePost.ts new file mode 100644 index 00000000..1b194b65 --- /dev/null +++ b/src/apis/community/deletePost.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, PostResponse, PostRequest } from "./api"; + +const useDeletePost = () => { + return useMutation({ + mutationFn: (variables) => communityApi.deletePost(variables), + }); +}; + +export default useDeletePost; \ No newline at end of file diff --git a/src/apis/community/getBoard.ts b/src/apis/community/getBoard.ts new file mode 100644 index 00000000..1ea2869d --- /dev/null +++ b/src/apis/community/getBoard.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { communityApi, BoardResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetBoard = (boardCode: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.community.board, boardCode, params], + queryFn: () => communityApi.getBoard({ boardCode, params }), + enabled: !!boardCode, + }); +}; + +export default useGetBoard; \ No newline at end of file diff --git a/src/apis/community/getBoardList.ts b/src/apis/community/getBoardList.ts new file mode 100644 index 00000000..bcb0d65b --- /dev/null +++ b/src/apis/community/getBoardList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { communityApi, BoardListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetBoardList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.community.boardList, params], + queryFn: () => communityApi.getBoardList(params ? { params } : {}), + }); +}; + +export default useGetBoardList; \ No newline at end of file diff --git a/src/apis/community/getPostDetail.ts b/src/apis/community/getPostDetail.ts new file mode 100644 index 00000000..ba5cc61a --- /dev/null +++ b/src/apis/community/getPostDetail.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { communityApi, PostDetailResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetPostDetail = (postId: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.community.postDetail, postId, params], + queryFn: () => communityApi.getPostDetail({ postId, params }), + enabled: !!postId, + }); +}; + +export default useGetPostDetail; \ No newline at end of file diff --git a/src/apis/community/index.ts b/src/apis/community/index.ts new file mode 100644 index 00000000..4d95a071 --- /dev/null +++ b/src/apis/community/index.ts @@ -0,0 +1,12 @@ +export { communityApi } from './api'; +export { default as deleteComment } from './deleteComment'; +export { default as deleteLikePost } from './deleteLikePost'; +export { default as deletePost } from './deletePost'; +export { default as getBoard } from './getBoard'; +export { default as getBoardList } from './getBoardList'; +export { default as getPostDetail } from './getPostDetail'; +export { default as patchUpdateComment } from './patchUpdateComment'; +export { default as patchUpdatePost } from './patchUpdatePost'; +export { default as postCreateComment } from './postCreateComment'; +export { default as postCreatePost } from './postCreatePost'; +export { default as postLikePost } from './postLikePost'; diff --git a/src/apis/community/patchUpdateComment.ts b/src/apis/community/patchUpdateComment.ts new file mode 100644 index 00000000..3a0fbfe6 --- /dev/null +++ b/src/apis/community/patchUpdateComment.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, UpdateCommentResponse, UpdateCommentRequest } from "./api"; + +const usePatchUpdateComment = () => { + return useMutation({ + mutationFn: (variables) => communityApi.patchUpdateComment(variables), + }); +}; + +export default usePatchUpdateComment; \ No newline at end of file diff --git a/src/apis/community/patchUpdatePost.ts b/src/apis/community/patchUpdatePost.ts new file mode 100644 index 00000000..acbdf578 --- /dev/null +++ b/src/apis/community/patchUpdatePost.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, UpdatePostResponse, UpdatePostRequest } from "./api"; + +const usePatchUpdatePost = () => { + return useMutation({ + mutationFn: (variables) => communityApi.patchUpdatePost(variables), + }); +}; + +export default usePatchUpdatePost; \ No newline at end of file diff --git a/src/apis/community/postCreateComment.ts b/src/apis/community/postCreateComment.ts new file mode 100644 index 00000000..215cd41e --- /dev/null +++ b/src/apis/community/postCreateComment.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, CreateCommentResponse, CreateCommentRequest } from "./api"; + +const usePostCreateComment = () => { + return useMutation({ + mutationFn: (data) => communityApi.postCreateComment({ data }), + }); +}; + +export default usePostCreateComment; \ No newline at end of file diff --git a/src/apis/community/postCreatePost.ts b/src/apis/community/postCreatePost.ts new file mode 100644 index 00000000..e83b02c4 --- /dev/null +++ b/src/apis/community/postCreatePost.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, CreatePostResponse, CreatePostRequest } from "./api"; + +const usePostCreatePost = () => { + return useMutation({ + mutationFn: (data) => communityApi.postCreatePost({ data }), + }); +}; + +export default usePostCreatePost; \ No newline at end of file diff --git a/src/apis/community/postLikePost.ts b/src/apis/community/postLikePost.ts new file mode 100644 index 00000000..f88f4c4c --- /dev/null +++ b/src/apis/community/postLikePost.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { communityApi, LikePostResponse, LikePostRequest } from "./api"; + +const usePostLikePost = () => { + return useMutation({ + mutationFn: (variables) => communityApi.postLikePost(variables), + }); +}; + +export default usePostLikePost; \ No newline at end of file diff --git a/src/apis/image-upload/api.ts b/src/apis/image-upload/api.ts new file mode 100644 index 00000000..a2024dda --- /dev/null +++ b/src/apis/image-upload/api.ts @@ -0,0 +1,67 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export type SlackNotificationResponse = void; + +export type SlackNotificationRequest = Record; + +export interface UploadLanguageTestReportResponse { + fileUrl: string; +} + +export type UploadLanguageTestReportRequest = Record; + +export interface UploadProfileImageResponse { + fileUrl: string; +} + +export type UploadProfileImageRequest = Record; + +export interface UploadProfileImageBeforeSignupResponse { + fileUrl: string; +} + +export type UploadProfileImageBeforeSignupRequest = Record; + +export interface UploadGpaReportResponse { + fileUrl: string; +} + +export type UploadGpaReportRequest = Record; + +export const imageUploadApi = { + postSlackNotification: async (params: { data?: SlackNotificationRequest }): Promise => { + const res = await axiosInstance.post( + `https://hooks.slack.com/services/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`, params?.data + ); + return res.data; + }, + + postUploadLanguageTestReport: async (params: { data?: UploadLanguageTestReportRequest }): Promise => { + const res = await axiosInstance.post( + `/file/language-test`, params?.data + ); + return res.data; + }, + + postUploadProfileImage: async (params: { data?: UploadProfileImageRequest }): Promise => { + const res = await axiosInstance.post( + `/file/profile/post`, params?.data + ); + return res.data; + }, + + postUploadProfileImageBeforeSignup: async (params: { data?: UploadProfileImageBeforeSignupRequest }): Promise => { + const res = await axiosInstance.post( + `/file/profile/pre`, params?.data + ); + return res.data; + }, + + postUploadGpaReport: async (params: { data?: UploadGpaReportRequest }): Promise => { + const res = await axiosInstance.post( + `/file/gpa`, params?.data + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/image-upload/index.ts b/src/apis/image-upload/index.ts new file mode 100644 index 00000000..95f3e6f9 --- /dev/null +++ b/src/apis/image-upload/index.ts @@ -0,0 +1,6 @@ +export { imageUploadApi } from './api'; +export { default as postSlackNotification } from './postSlackNotification'; +export { default as postUploadGpaReport } from './postUploadGpaReport'; +export { default as postUploadLanguageTestReport } from './postUploadLanguageTestReport'; +export { default as postUploadProfileImage } from './postUploadProfileImage'; +export { default as postUploadProfileImageBeforeSignup } from './postUploadProfileImageBeforeSignup'; diff --git a/src/apis/image-upload/postSlackNotification.ts b/src/apis/image-upload/postSlackNotification.ts new file mode 100644 index 00000000..c973ec12 --- /dev/null +++ b/src/apis/image-upload/postSlackNotification.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { imageUploadApi, SlackNotificationResponse, SlackNotificationRequest } from "./api"; + +const usePostSlackNotification = () => { + return useMutation({ + mutationFn: (data) => imageUploadApi.postSlackNotification({ data }), + }); +}; + +export default usePostSlackNotification; \ No newline at end of file diff --git a/src/apis/image-upload/postUploadGpaReport.ts b/src/apis/image-upload/postUploadGpaReport.ts new file mode 100644 index 00000000..06ed6ece --- /dev/null +++ b/src/apis/image-upload/postUploadGpaReport.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { imageUploadApi, UploadGpaReportResponse, UploadGpaReportRequest } from "./api"; + +const usePostUploadGpaReport = () => { + return useMutation({ + mutationFn: (data) => imageUploadApi.postUploadGpaReport({ data }), + }); +}; + +export default usePostUploadGpaReport; \ No newline at end of file diff --git a/src/apis/image-upload/postUploadLanguageTestReport.ts b/src/apis/image-upload/postUploadLanguageTestReport.ts new file mode 100644 index 00000000..7b36f675 --- /dev/null +++ b/src/apis/image-upload/postUploadLanguageTestReport.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { imageUploadApi, UploadLanguageTestReportResponse, UploadLanguageTestReportRequest } from "./api"; + +const usePostUploadLanguageTestReport = () => { + return useMutation({ + mutationFn: (data) => imageUploadApi.postUploadLanguageTestReport({ data }), + }); +}; + +export default usePostUploadLanguageTestReport; \ No newline at end of file diff --git a/src/apis/image-upload/postUploadProfileImage.ts b/src/apis/image-upload/postUploadProfileImage.ts new file mode 100644 index 00000000..72db4ae2 --- /dev/null +++ b/src/apis/image-upload/postUploadProfileImage.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { imageUploadApi, UploadProfileImageResponse, UploadProfileImageRequest } from "./api"; + +const usePostUploadProfileImage = () => { + return useMutation({ + mutationFn: (data) => imageUploadApi.postUploadProfileImage({ data }), + }); +}; + +export default usePostUploadProfileImage; \ No newline at end of file diff --git a/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts new file mode 100644 index 00000000..edf2b95c --- /dev/null +++ b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { imageUploadApi, UploadProfileImageBeforeSignupResponse, UploadProfileImageBeforeSignupRequest } from "./api"; + +const usePostUploadProfileImageBeforeSignup = () => { + return useMutation({ + mutationFn: (data) => imageUploadApi.postUploadProfileImageBeforeSignup({ data }), + }); +}; + +export default usePostUploadProfileImageBeforeSignup; \ No newline at end of file diff --git a/src/apis/kakao-api/api.ts b/src/apis/kakao-api/api.ts new file mode 100644 index 00000000..51eda46d --- /dev/null +++ b/src/apis/kakao-api/api.ts @@ -0,0 +1,33 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export type KakaoUserIdsResponse = void; + +export type KakaoUnlinkResponse = void; + +export type KakaoUnlinkRequest = Record; + +export type KakaoInfoResponse = void; + +export const kakaoApiApi = { + getKakaoUserIds: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `https://kapi.kakao.com/v1/user/ids?order=dsc`, { params: params?.params } + ); + return res.data; + }, + + postKakaoUnlink: async (params: { data?: KakaoUnlinkRequest }): Promise => { + const res = await axiosInstance.post( + `https://kapi.kakao.com/v1/user/unlink?target_id_type=user_id&target_id=3715136239`, params?.data + ); + return res.data; + }, + + getKakaoInfo: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `https://kapi.kakao.com/v2/user/me?property_keys=["kakao_account.email"]&target_id_type=user_id&target_id=3715136239`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/kakao-api/getKakaoInfo.ts b/src/apis/kakao-api/getKakaoInfo.ts new file mode 100644 index 00000000..92c82449 --- /dev/null +++ b/src/apis/kakao-api/getKakaoInfo.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { kakaoApiApi, KakaoInfoResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetKakaoInfo = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys['kakao-api'].kakaoInfo, params], + queryFn: () => kakaoApiApi.getKakaoInfo(params ? { params } : {}), + }); +}; + +export default useGetKakaoInfo; \ No newline at end of file diff --git a/src/apis/kakao-api/getKakaoUserIds.ts b/src/apis/kakao-api/getKakaoUserIds.ts new file mode 100644 index 00000000..dc85cac5 --- /dev/null +++ b/src/apis/kakao-api/getKakaoUserIds.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { kakaoApiApi, KakaoUserIdsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetKakaoUserIds = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys['kakao-api'].kakaoUserIds, params], + queryFn: () => kakaoApiApi.getKakaoUserIds(params ? { params } : {}), + }); +}; + +export default useGetKakaoUserIds; \ No newline at end of file diff --git a/src/apis/kakao-api/index.ts b/src/apis/kakao-api/index.ts new file mode 100644 index 00000000..bd6c3ae8 --- /dev/null +++ b/src/apis/kakao-api/index.ts @@ -0,0 +1,4 @@ +export { kakaoApiApi } from './api'; +export { default as getKakaoInfo } from './getKakaoInfo'; +export { default as getKakaoUserIds } from './getKakaoUserIds'; +export { default as postKakaoUnlink } from './postKakaoUnlink'; diff --git a/src/apis/kakao-api/postKakaoUnlink.ts b/src/apis/kakao-api/postKakaoUnlink.ts new file mode 100644 index 00000000..c20d19d0 --- /dev/null +++ b/src/apis/kakao-api/postKakaoUnlink.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { kakaoApiApi, KakaoUnlinkResponse, KakaoUnlinkRequest } from "./api"; + +const usePostKakaoUnlink = () => { + return useMutation({ + mutationFn: (data) => kakaoApiApi.postKakaoUnlink({ data }), + }); +}; + +export default usePostKakaoUnlink; \ No newline at end of file diff --git a/src/apis/mentor/api.ts b/src/apis/mentor/api.ts new file mode 100644 index 00000000..1a11d710 --- /dev/null +++ b/src/apis/mentor/api.ts @@ -0,0 +1,230 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface MatchedMentorsResponseContentItem { + id: number; + roomId: number; + nickname: string; + profileImageUrl: string; + country: string; + universityName: string; + term: string; + menteeCount: number; + hasBadge: boolean; + introduction: string; + channels: MatchedMentorsResponseContentItemChannelsItem[]; + isApplied: boolean; +} + +export interface MatchedMentorsResponseContentItemChannelsItem { + type: string; + url: string; +} + +export interface MatchedMentorsResponse { + content: MatchedMentorsResponseContentItem[]; + nextPageNumber: number; +} + +export interface ApplyMentoringResponse { + mentoringId: number; +} + +export type ApplyMentoringRequest = Record; + +export interface ConfirmMentoringResponse { + checkedMentoringIds: number[]; +} + +export type ConfirmMentoringRequest = Record; + +export interface AppliedMentoringsResponseContentItem { + mentoringId: number; + profileImageUrl: null; + nickname: string; + isChecked: boolean; + createdAt: string; + chatRoomId: number; +} + +export interface AppliedMentoringsResponse { + content: AppliedMentoringsResponseContentItem[]; + nextPageNumber: number; +} + +export interface MentorListResponseContentItem { + id: number; + profileImageUrl: string; + nickname: string; + country: string; + universityName: string; + term: string; + menteeCount: number; + hasBadge: boolean; + introduction: string; + channels: MentorListResponseContentItemChannelsItem[]; + isApplied: boolean; +} + +export interface MentorListResponseContentItemChannelsItem { + type: string; + url: string; +} + +export interface MentorListResponse { + nextPageNumber: number; + content: MentorListResponseContentItem[]; +} + +export interface MentorDetailResponseChannelsItem { + type: string; + url: string; +} + +export interface MentorDetailResponse { + id: number; + profileImageUrl: string; + nickname: string; + country: string; + universityName: string; + term: string; + menteeCount: number; + hasBadge: boolean; + introduction: string; + channels: MentorDetailResponseChannelsItem[]; + passTip: string; + isApplied: boolean; +} + +export interface MyMentorPageResponseChannelsItem { + type: string; + url: string; +} + +export interface MyMentorPageResponse { + id: number; + profileImageUrl: null; + nickname: string; + country: string; + universityName: string; + term: string; + menteeCount: number; + hasBadge: boolean; + introduction: string; + passTip: string; + channels: MyMentorPageResponseChannelsItem[]; +} + +export type UpdateMyMentorPageResponse = Record; + +export type UpdateMyMentorPageRequest = Record; + +export interface MentoringStatusResponse { + mentoringId: number; +} + +export type MentoringStatusRequest = Record; + +export interface ReceivedMentoringsResponseContentItem { + mentoringId: number; + profileImageUrl: null; + nickname: string; + isChecked: boolean; + verifyStatus: string; + createdAt: string; +} + +export interface ReceivedMentoringsResponse { + content: ReceivedMentoringsResponseContentItem[]; + nextPageNumber: number; +} + +export interface UnconfirmedMentoringCountResponse { + uncheckedCount: number; +} + +export const mentorApi = { + getMatchedMentors: async (params: { defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentee/mentorings/matched-mentors?size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } + ); + return res.data; + }, + + postApplyMentoring: async (params: { data?: ApplyMentoringRequest }): Promise => { + const res = await axiosInstance.post( + `/mentee/mentorings`, params?.data + ); + return res.data; + }, + + patchConfirmMentoring: async (params: { data?: ConfirmMentoringRequest }): Promise => { + const res = await axiosInstance.patch( + `/mentee/mentorings/check`, params?.data + ); + return res.data; + }, + + getAppliedMentorings: async (params: { verifyStatus: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentee/mentorings?verify-status=${params.verifyStatus}&size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } + ); + return res.data; + }, + + getMentorList: async (params: { defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentors?region=미주권&size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } + ); + return res.data; + }, + + getMentorDetail: async (params: { mentorId: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentors/${params.mentorId}`, { params: params?.params } + ); + return res.data; + }, + + getMyMentorPage: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentor/my`, { params: params?.params } + ); + return res.data; + }, + + putUpdateMyMentorPage: async (params: { data?: UpdateMyMentorPageRequest }): Promise => { + const res = await axiosInstance.put( + `/mentor/my`, params?.data + ); + return res.data; + }, + + patchMentoringStatus: async (params: { mentoringId: string | number, data?: MentoringStatusRequest }): Promise => { + const res = await axiosInstance.patch( + `/mentor/mentorings/${params.mentoringId}`, params?.data + ); + return res.data; + }, + + patchConfirmMentoring: async (params: { data?: ConfirmMentoringRequest }): Promise => { + const res = await axiosInstance.patch( + `/mentor/mentorings/check`, params?.data + ); + return res.data; + }, + + getReceivedMentorings: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentor/mentorings`, { params: params?.params } + ); + return res.data; + }, + + getUnconfirmedMentoringCount: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/mentor/mentorings/check`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/mentor/getAppliedMentorings.ts b/src/apis/mentor/getAppliedMentorings.ts new file mode 100644 index 00000000..54a9d70b --- /dev/null +++ b/src/apis/mentor/getAppliedMentorings.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, AppliedMentoringsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetAppliedMentorings = (verifyStatus: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.appliedMentorings, verifyStatus, defaultSize, defaultPage, params], + queryFn: () => mentorApi.getAppliedMentorings({ verifyStatus, defaultSize, defaultPage, params }), + enabled: !!verifyStatus && !!defaultSize && !!defaultPage, + }); +}; + +export default useGetAppliedMentorings; \ No newline at end of file diff --git a/src/apis/mentor/getMatchedMentors.ts b/src/apis/mentor/getMatchedMentors.ts new file mode 100644 index 00000000..40ff31b9 --- /dev/null +++ b/src/apis/mentor/getMatchedMentors.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, MatchedMentorsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetMatchedMentors = (defaultSize: string | number, defaultPage: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.matchedMentors, defaultSize, defaultPage, params], + queryFn: () => mentorApi.getMatchedMentors({ defaultSize, defaultPage, params }), + enabled: !!defaultSize && !!defaultPage, + }); +}; + +export default useGetMatchedMentors; \ No newline at end of file diff --git a/src/apis/mentor/getMentorDetail.ts b/src/apis/mentor/getMentorDetail.ts new file mode 100644 index 00000000..88f22b9e --- /dev/null +++ b/src/apis/mentor/getMentorDetail.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, MentorDetailResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetMentorDetail = (mentorId: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.mentorDetail, mentorId, params], + queryFn: () => mentorApi.getMentorDetail({ mentorId, params }), + enabled: !!mentorId, + }); +}; + +export default useGetMentorDetail; \ No newline at end of file diff --git a/src/apis/mentor/getMentorList.ts b/src/apis/mentor/getMentorList.ts new file mode 100644 index 00000000..465458c0 --- /dev/null +++ b/src/apis/mentor/getMentorList.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, MentorListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetMentorList = (defaultSize: string | number, defaultPage: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.mentorList, defaultSize, defaultPage, params], + queryFn: () => mentorApi.getMentorList({ defaultSize, defaultPage, params }), + enabled: !!defaultSize && !!defaultPage, + }); +}; + +export default useGetMentorList; \ No newline at end of file diff --git a/src/apis/mentor/getMyMentorPage.ts b/src/apis/mentor/getMyMentorPage.ts new file mode 100644 index 00000000..db6d1b42 --- /dev/null +++ b/src/apis/mentor/getMyMentorPage.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, MyMentorPageResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetMyMentorPage = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.myMentorPage, params], + queryFn: () => mentorApi.getMyMentorPage(params ? { params } : {}), + }); +}; + +export default useGetMyMentorPage; \ No newline at end of file diff --git a/src/apis/mentor/getReceivedMentorings.ts b/src/apis/mentor/getReceivedMentorings.ts new file mode 100644 index 00000000..81d875cf --- /dev/null +++ b/src/apis/mentor/getReceivedMentorings.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, ReceivedMentoringsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetReceivedMentorings = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.receivedMentorings, params], + queryFn: () => mentorApi.getReceivedMentorings(params ? { params } : {}), + }); +}; + +export default useGetReceivedMentorings; \ No newline at end of file diff --git a/src/apis/mentor/getUnconfirmedMentoringCount.ts b/src/apis/mentor/getUnconfirmedMentoringCount.ts new file mode 100644 index 00000000..7c3b9c1b --- /dev/null +++ b/src/apis/mentor/getUnconfirmedMentoringCount.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { mentorApi, UnconfirmedMentoringCountResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetUnconfirmedMentoringCount = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.mentor.unconfirmedMentoringCount, params], + queryFn: () => mentorApi.getUnconfirmedMentoringCount(params ? { params } : {}), + }); +}; + +export default useGetUnconfirmedMentoringCount; \ No newline at end of file diff --git a/src/apis/mentor/index.ts b/src/apis/mentor/index.ts new file mode 100644 index 00000000..cfcceb89 --- /dev/null +++ b/src/apis/mentor/index.ts @@ -0,0 +1,12 @@ +export { mentorApi } from './api'; +export { default as getAppliedMentorings } from './getAppliedMentorings'; +export { default as getMatchedMentors } from './getMatchedMentors'; +export { default as getMentorDetail } from './getMentorDetail'; +export { default as getMentorList } from './getMentorList'; +export { default as getMyMentorPage } from './getMyMentorPage'; +export { default as getReceivedMentorings } from './getReceivedMentorings'; +export { default as getUnconfirmedMentoringCount } from './getUnconfirmedMentoringCount'; +export { default as patchConfirmMentoring } from './patchConfirmMentoring'; +export { default as patchMentoringStatus } from './patchMentoringStatus'; +export { default as postApplyMentoring } from './postApplyMentoring'; +export { default as putUpdateMyMentorPage } from './putUpdateMyMentorPage'; diff --git a/src/apis/mentor/legacy/patchConfirmMentoring.ts b/src/apis/mentor/legacy/patchConfirmMentoring.ts new file mode 100644 index 00000000..43e07f3f --- /dev/null +++ b/src/apis/mentor/legacy/patchConfirmMentoring.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, ConfirmMentoringResponse, ConfirmMentoringRequest } from "./api"; + +const usePatchConfirmMentoring = () => { + return useMutation({ + mutationFn: (data) => mentorApi.patchConfirmMentoring({ data }), + }); +}; + +export default usePatchConfirmMentoring; \ No newline at end of file diff --git a/src/apis/mentor/patchConfirmMentoring.ts b/src/apis/mentor/patchConfirmMentoring.ts new file mode 100644 index 00000000..43e07f3f --- /dev/null +++ b/src/apis/mentor/patchConfirmMentoring.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, ConfirmMentoringResponse, ConfirmMentoringRequest } from "./api"; + +const usePatchConfirmMentoring = () => { + return useMutation({ + mutationFn: (data) => mentorApi.patchConfirmMentoring({ data }), + }); +}; + +export default usePatchConfirmMentoring; \ No newline at end of file diff --git a/src/apis/mentor/patchMentoringStatus.ts b/src/apis/mentor/patchMentoringStatus.ts new file mode 100644 index 00000000..eec04d74 --- /dev/null +++ b/src/apis/mentor/patchMentoringStatus.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, MentoringStatusResponse, MentoringStatusRequest } from "./api"; + +const usePatchMentoringStatus = () => { + return useMutation({ + mutationFn: (variables) => mentorApi.patchMentoringStatus(variables), + }); +}; + +export default usePatchMentoringStatus; \ No newline at end of file diff --git a/src/apis/mentor/postApplyMentoring.ts b/src/apis/mentor/postApplyMentoring.ts new file mode 100644 index 00000000..f5a54e52 --- /dev/null +++ b/src/apis/mentor/postApplyMentoring.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, ApplyMentoringResponse, ApplyMentoringRequest } from "./api"; + +const usePostApplyMentoring = () => { + return useMutation({ + mutationFn: (data) => mentorApi.postApplyMentoring({ data }), + }); +}; + +export default usePostApplyMentoring; \ No newline at end of file diff --git a/src/apis/mentor/putUpdateMyMentorPage.ts b/src/apis/mentor/putUpdateMyMentorPage.ts new file mode 100644 index 00000000..a3cd5a31 --- /dev/null +++ b/src/apis/mentor/putUpdateMyMentorPage.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, UpdateMyMentorPageResponse, UpdateMyMentorPageRequest } from "./api"; + +const usePutUpdateMyMentorPage = () => { + return useMutation({ + mutationFn: (data) => mentorApi.putUpdateMyMentorPage({ data }), + }); +}; + +export default usePutUpdateMyMentorPage; \ No newline at end of file diff --git a/src/apis/news/api.ts b/src/apis/news/api.ts new file mode 100644 index 00000000..13a68b0b --- /dev/null +++ b/src/apis/news/api.ts @@ -0,0 +1,79 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface NewsListResponseNewsResponseListItem { + id: number; + title: string; + description: string; + url: string; + thumbnailUrl: string; + updatedAt: string; +} + +export interface NewsListResponse { + newsResponseList: NewsListResponseNewsResponseListItem[]; +} + +export interface NewsResponse { + id: number; +} + +export interface UpdateNewsResponse { + id: number; +} + +export type UpdateNewsRequest = Record; + +export type LikeNewsResponse = void; + +export type LikeNewsRequest = Record; + +export interface CreateNewsResponse { + id: number; +} + +export type CreateNewsRequest = Record; + +export const newsApi = { + getNewsList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/news?author-id=6`, { params: params?.params } + ); + return res.data; + }, + + deleteNews: async (params: { newsId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/news/${params.newsId}` + ); + return res.data; + }, + + putUpdateNews: async (params: { newsId: string | number, data?: UpdateNewsRequest }): Promise => { + const res = await axiosInstance.put( + `/news/${params.newsId}`, params?.data + ); + return res.data; + }, + + postLikeNews: async (params: { newsId: string | number, data?: LikeNewsRequest }): Promise => { + const res = await axiosInstance.post( + `/news/${params.newsId}/like`, params?.data + ); + return res.data; + }, + + deleteLikeNews: async (params: { newsId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/news/${params.newsId}/like` + ); + return res.data; + }, + + postCreateNews: async (params: { data?: CreateNewsRequest }): Promise => { + const res = await axiosInstance.post( + `/news`, params?.data + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/news/deleteLikeNews.ts b/src/apis/news/deleteLikeNews.ts new file mode 100644 index 00000000..9e0ad49c --- /dev/null +++ b/src/apis/news/deleteLikeNews.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { newsApi, LikeNewsResponse, LikeNewsRequest } from "./api"; + +const useDeleteLikeNews = () => { + return useMutation({ + mutationFn: (variables) => newsApi.deleteLikeNews(variables), + }); +}; + +export default useDeleteLikeNews; \ No newline at end of file diff --git a/src/apis/news/deleteNews.ts b/src/apis/news/deleteNews.ts new file mode 100644 index 00000000..854b6392 --- /dev/null +++ b/src/apis/news/deleteNews.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { newsApi, NewsResponse, NewsRequest } from "./api"; + +const useDeleteNews = () => { + return useMutation({ + mutationFn: (variables) => newsApi.deleteNews(variables), + }); +}; + +export default useDeleteNews; \ No newline at end of file diff --git a/src/apis/news/getNewsList.ts b/src/apis/news/getNewsList.ts new file mode 100644 index 00000000..bd2f587d --- /dev/null +++ b/src/apis/news/getNewsList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { newsApi, NewsListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetNewsList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.news.newsList, params], + queryFn: () => newsApi.getNewsList(params ? { params } : {}), + }); +}; + +export default useGetNewsList; \ No newline at end of file diff --git a/src/apis/news/index.ts b/src/apis/news/index.ts new file mode 100644 index 00000000..dc313953 --- /dev/null +++ b/src/apis/news/index.ts @@ -0,0 +1,7 @@ +export { newsApi } from './api'; +export { default as deleteLikeNews } from './deleteLikeNews'; +export { default as deleteNews } from './deleteNews'; +export { default as getNewsList } from './getNewsList'; +export { default as postCreateNews } from './postCreateNews'; +export { default as postLikeNews } from './postLikeNews'; +export { default as putUpdateNews } from './putUpdateNews'; diff --git a/src/apis/news/postCreateNews.ts b/src/apis/news/postCreateNews.ts new file mode 100644 index 00000000..075351f2 --- /dev/null +++ b/src/apis/news/postCreateNews.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { newsApi, CreateNewsResponse, CreateNewsRequest } from "./api"; + +const usePostCreateNews = () => { + return useMutation({ + mutationFn: (data) => newsApi.postCreateNews({ data }), + }); +}; + +export default usePostCreateNews; \ No newline at end of file diff --git a/src/apis/news/postLikeNews.ts b/src/apis/news/postLikeNews.ts new file mode 100644 index 00000000..9aaf4c53 --- /dev/null +++ b/src/apis/news/postLikeNews.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { newsApi, LikeNewsResponse, LikeNewsRequest } from "./api"; + +const usePostLikeNews = () => { + return useMutation({ + mutationFn: (variables) => newsApi.postLikeNews(variables), + }); +}; + +export default usePostLikeNews; \ No newline at end of file diff --git a/src/apis/news/putUpdateNews.ts b/src/apis/news/putUpdateNews.ts new file mode 100644 index 00000000..d7fedea7 --- /dev/null +++ b/src/apis/news/putUpdateNews.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { newsApi, UpdateNewsResponse, UpdateNewsRequest } from "./api"; + +const usePutUpdateNews = () => { + return useMutation({ + mutationFn: (variables) => newsApi.putUpdateNews(variables), + }); +}; + +export default usePutUpdateNews; \ No newline at end of file diff --git a/src/apis/queryKeys.ts b/src/apis/queryKeys.ts new file mode 100644 index 00000000..2a1d2c8b --- /dev/null +++ b/src/apis/queryKeys.ts @@ -0,0 +1,132 @@ +/** + * React Query Keys + * Bruno 폴더 구조를 기반으로 자동 생성됨 + */ + +export const QueryKeys = { + Auth: { + folder: 'Auth.folder' as const, + signOut: 'Auth.signOut' as const, + appleAuth: 'Auth.appleAuth' as const, + refreshToken: 'Auth.refreshToken' as const, + emailLogin: 'Auth.emailLogin' as const, + emailVerification: 'Auth.emailVerification' as const, + kakaoAuth: 'Auth.kakaoAuth' as const, + account: 'Auth.account' as const, + signUp: 'Auth.signUp' as const, + }, + news: { + folder: 'news.folder' as const, + newsList: 'news.newsList' as const, + news: 'news.news' as const, + updateNews: 'news.updateNews' as const, + likeNews: 'news.likeNews' as const, + createNews: 'news.createNews' as const, + }, + reports: { + folder: 'reports.folder' as const, + report: 'reports.report' as const, + }, + chat: { + folder: 'chat.folder' as const, + chatMessages: 'chat.chatMessages' as const, + chatRooms: 'chat.chatRooms' as const, + readChatRoom: 'chat.readChatRoom' as const, + chatPartner: 'chat.chatPartner' as const, + }, + universities: { + folder: 'universities.folder' as const, + recommendedUniversities: 'universities.recommendedUniversities' as const, + wishList: 'universities.wishList' as const, + wish: 'universities.wish' as const, + addWish: 'universities.addWish' as const, + isWish: 'universities.isWish' as const, + universityDetail: 'universities.universityDetail' as const, + searchText: 'universities.searchText' as const, + searchFilter: 'universities.searchFilter' as const, + byRegionCountry: 'universities.byRegionCountry' as const, + }, + MyPage: { + folder: 'MyPage.folder' as const, + interestedRegionCountry: 'MyPage.interestedRegionCountry' as const, + profile: 'MyPage.profile' as const, + password: 'MyPage.password' as const, + }, + applications: { + folder: 'applications.folder' as const, + competitors: 'applications.competitors' as const, + submitApplication: 'applications.submitApplication' as const, + applicants: 'applications.applicants' as const, + }, + community: { + folder: 'community.folder' as const, + boardList: 'community.boardList' as const, + board: 'community.board' as const, + comment: 'community.comment' as const, + updateComment: 'community.updateComment' as const, + createComment: 'community.createComment' as const, + post: 'community.post' as const, + updatePost: 'community.updatePost' as const, + createPost: 'community.createPost' as const, + postDetail: 'community.postDetail' as const, + likePost: 'community.likePost' as const, + }, + Scores: { + folder: 'Scores.folder' as const, + createLanguageTest: 'Scores.createLanguageTest' as const, + languageTestList: 'Scores.languageTestList' as const, + createGpa: 'Scores.createGpa' as const, + gpaList: 'Scores.gpaList' as const, + }, + Admin: { + folder: 'Admin.folder' as const, + verifyLanguageTest: 'Admin.verifyLanguageTest' as const, + languageTestList: 'Admin.languageTestList' as const, + verifyGpa: 'Admin.verifyGpa' as const, + gpaList: 'Admin.gpaList' as const, + }, + users: { + folder: 'users.folder' as const, + nicknameExists: 'users.nicknameExists' as const, + blockUser: 'users.blockUser' as const, + unblockUser: 'users.unblockUser' as const, + blockedUsers: 'users.blockedUsers' as const, + }, + mentor: { + folder: 'mentor.folder' as const, + matchedMentors: 'mentor.matchedMentors' as const, + applyMentoring: 'mentor.applyMentoring' as const, + confirmMentoring: 'mentor.confirmMentoring' as const, + appliedMentorings: 'mentor.appliedMentorings' as const, + mentorList: 'mentor.mentorList' as const, + mentorDetail: 'mentor.mentorDetail' as const, + myMentorPage: 'mentor.myMentorPage' as const, + updateMyMentorPage: 'mentor.updateMyMentorPage' as const, + mentoringStatus: 'mentor.mentoringStatus' as const, + receivedMentorings: 'mentor.receivedMentorings' as const, + unconfirmedMentoringCount: 'mentor.unconfirmedMentoringCount' as const, + }, + 'kakao-api': { + folder: 'kakao-api.folder' as const, + kakaoUserIds: 'kakao-api.kakaoUserIds' as const, + kakaoUnlink: 'kakao-api.kakaoUnlink' as const, + kakaoInfo: 'kakao-api.kakaoInfo' as const, + }, + 'collection.bru': { + collection: 'collection.bru.collection' as const, + }, + environments: { + dev: 'environments.dev' as const, + local: 'environments.local' as const, + }, + 'image-upload': { + folder: 'image-upload.folder' as const, + slackNotification: 'image-upload.slackNotification' as const, + uploadLanguageTestReport: 'image-upload.uploadLanguageTestReport' as const, + uploadProfileImage: 'image-upload.uploadProfileImage' as const, + uploadProfileImageBeforeSignup: 'image-upload.uploadProfileImageBeforeSignup' as const, + uploadGpaReport: 'image-upload.uploadGpaReport' as const, + }, +} as const; + +export type QueryKey = typeof QueryKeys[keyof typeof QueryKeys]; \ No newline at end of file diff --git a/src/apis/reports/api.ts b/src/apis/reports/api.ts new file mode 100644 index 00000000..39bd649c --- /dev/null +++ b/src/apis/reports/api.ts @@ -0,0 +1,15 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export type ReportResponse = Record; + +export type ReportRequest = Record; + +export const reportsApi = { + postReport: async (params: { data?: ReportRequest }): Promise => { + const res = await axiosInstance.post( + `/reports`, params?.data + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/reports/index.ts b/src/apis/reports/index.ts new file mode 100644 index 00000000..c8d54d63 --- /dev/null +++ b/src/apis/reports/index.ts @@ -0,0 +1,2 @@ +export { reportsApi } from './api'; +export { default as postReport } from './postReport'; diff --git a/src/apis/reports/postReport.ts b/src/apis/reports/postReport.ts new file mode 100644 index 00000000..76fd804f --- /dev/null +++ b/src/apis/reports/postReport.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { reportsApi, ReportResponse, ReportRequest } from "./api"; + +const usePostReport = () => { + return useMutation({ + mutationFn: (data) => reportsApi.postReport({ data }), + }); +}; + +export default usePostReport; \ No newline at end of file diff --git a/src/apis/universities/api.ts b/src/apis/universities/api.ts new file mode 100644 index 00000000..c11d4141 --- /dev/null +++ b/src/apis/universities/api.ts @@ -0,0 +1,196 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface RecommendedUniversitiesResponseRecommendedUniversitiesItem { + id: number; + term: string; + koreanName: string; + region: string; + country: string; + logoImageUrl: string; + backgroundImageUrl: string; + studentCapacity: number; + languageRequirements: RecommendedUniversitiesResponseRecommendedUniversitiesItemLanguageRequirementsItem[]; +} + +export interface RecommendedUniversitiesResponseRecommendedUniversitiesItemLanguageRequirementsItem { + languageTestType: string; + minScore: string; +} + +export interface RecommendedUniversitiesResponse { + recommendedUniversities: RecommendedUniversitiesResponseRecommendedUniversitiesItem[]; +} + +export interface WishListResponseItem { + id: number; + term: string; + koreanName: string; + region: string; + country: string; + logoImageUrl: string; + backgroundImageUrl: string; + studentCapacity: number; + languageRequirements: WishListResponseItemLanguageRequirementsItem[]; +} + +export interface WishListResponseItemLanguageRequirementsItem { + languageTestType: string; + minScore: string; +} + +export interface WishListResponse { + 0: WishListResponse0; + 1: WishListResponse1; +} + +export type WishResponse = void; + +export type AddWishResponse = void; + +export type AddWishRequest = Record; + +export type IsWishResponse = void; + +export interface UniversityDetailResponseLanguageRequirementsItem { + languageTestType: string; + minScore: string; +} + +export interface UniversityDetailResponse { + id: number; + term: string; + koreanName: string; + englishName: string; + formatName: string; + region: string; + country: string; + homepageUrl: string; + logoImageUrl: string; + backgroundImageUrl: string; + detailsForLocal: string; + studentCapacity: number; + tuitionFeeType: string; + semesterAvailableForDispatch: string; + languageRequirements: UniversityDetailResponseLanguageRequirementsItem[]; + detailsForLanguage: string; + gpaRequirement: string; + gpaRequirementCriteria: string; + semesterRequirement: string; + detailsForApply: null; + detailsForMajor: string; + detailsForAccommodation: null; + detailsForEnglishCourse: null; + details: string; + accommodationUrl: string; + englishCourseUrl: string; +} + +export interface SearchTextResponseUnivApplyInfoPreviewsItem { + id: number; + term: string; + koreanName: string; + region: string; + country: string; + logoImageUrl: string; + backgroundImageUrl: string; + studentCapacity: number; + languageRequirements: SearchTextResponseUnivApplyInfoPreviewsItemLanguageRequirementsItem[]; +} + +export interface SearchTextResponseUnivApplyInfoPreviewsItemLanguageRequirementsItem { + languageTestType: string; + minScore: string; +} + +export interface SearchTextResponse { + univApplyInfoPreviews: SearchTextResponseUnivApplyInfoPreviewsItem[]; +} + +export interface SearchFilterResponseUnivApplyInfoPreviewsItem { + id: number; + term: string; + koreanName: string; + region: string; + country: string; + logoImageUrl: string; + backgroundImageUrl: string; + studentCapacity: number; + languageRequirements: SearchFilterResponseUnivApplyInfoPreviewsItemLanguageRequirementsItem[]; +} + +export interface SearchFilterResponseUnivApplyInfoPreviewsItemLanguageRequirementsItem { + languageTestType: string; + minScore: string; +} + +export interface SearchFilterResponse { + univApplyInfoPreviews: SearchFilterResponseUnivApplyInfoPreviewsItem[]; +} + +export type ByRegionCountryResponse = void; + +export const universitiesApi = { + getRecommendedUniversities: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/recommend`, { params: params?.params } + ); + return res.data; + }, + + getWishList: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/like`, { params: params?.params } + ); + return res.data; + }, + + deleteWish: async (params: { univApplyInfoId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/univ-apply-infos/${params.univApplyInfoId}/like` + ); + return res.data; + }, + + postAddWish: async (params: { univApplyInfoId: string | number, data?: AddWishRequest }): Promise => { + const res = await axiosInstance.post( + `/univ-apply-infos/${params.univApplyInfoId}/like`, params?.data + ); + return res.data; + }, + + getIsWish: async (params: { univApplyInfoId: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/${params.univApplyInfoId}/like`, { params: params?.params } + ); + return res.data; + }, + + getUniversityDetail: async (params: { univApplyInfoId: string | number, params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/${params.univApplyInfoId}`, { params: params?.params } + ); + return res.data; + }, + + getSearchText: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/search/text?value=괌`, { params: params?.params } + ); + return res.data; + }, + + getSearchFilter: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/univ-apply-infos/search/filter`, { params: params?.params } + ); + return res.data; + }, + + getByRegionCountry: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/universities/search`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/universities/deleteWish.ts b/src/apis/universities/deleteWish.ts new file mode 100644 index 00000000..e4c88b1f --- /dev/null +++ b/src/apis/universities/deleteWish.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { universitiesApi, WishResponse, WishRequest } from "./api"; + +const useDeleteWish = () => { + return useMutation({ + mutationFn: (variables) => universitiesApi.deleteWish(variables), + }); +}; + +export default useDeleteWish; \ No newline at end of file diff --git a/src/apis/universities/getByRegionCountry.ts b/src/apis/universities/getByRegionCountry.ts new file mode 100644 index 00000000..baa84596 --- /dev/null +++ b/src/apis/universities/getByRegionCountry.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, ByRegionCountryResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetByRegionCountry = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.byRegionCountry, params], + queryFn: () => universitiesApi.getByRegionCountry(params ? { params } : {}), + }); +}; + +export default useGetByRegionCountry; \ No newline at end of file diff --git a/src/apis/universities/getIsWish.ts b/src/apis/universities/getIsWish.ts new file mode 100644 index 00000000..567a0887 --- /dev/null +++ b/src/apis/universities/getIsWish.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, IsWishResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetIsWish = (univApplyInfoId: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.isWish, univApplyInfoId, params], + queryFn: () => universitiesApi.getIsWish({ univApplyInfoId, params }), + enabled: !!univApplyInfoId, + }); +}; + +export default useGetIsWish; \ No newline at end of file diff --git a/src/apis/universities/getRecommendedUniversities.ts b/src/apis/universities/getRecommendedUniversities.ts new file mode 100644 index 00000000..014e50e7 --- /dev/null +++ b/src/apis/universities/getRecommendedUniversities.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, RecommendedUniversitiesResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetRecommendedUniversities = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.recommendedUniversities, params], + queryFn: () => universitiesApi.getRecommendedUniversities(params ? { params } : {}), + }); +}; + +export default useGetRecommendedUniversities; \ No newline at end of file diff --git a/src/apis/universities/getSearchFilter.ts b/src/apis/universities/getSearchFilter.ts new file mode 100644 index 00000000..34befa8e --- /dev/null +++ b/src/apis/universities/getSearchFilter.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, SearchFilterResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetSearchFilter = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.searchFilter, params], + queryFn: () => universitiesApi.getSearchFilter(params ? { params } : {}), + }); +}; + +export default useGetSearchFilter; \ No newline at end of file diff --git a/src/apis/universities/getSearchText.ts b/src/apis/universities/getSearchText.ts new file mode 100644 index 00000000..30967c5e --- /dev/null +++ b/src/apis/universities/getSearchText.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, SearchTextResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetSearchText = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.searchText, params], + queryFn: () => universitiesApi.getSearchText(params ? { params } : {}), + }); +}; + +export default useGetSearchText; \ No newline at end of file diff --git a/src/apis/universities/getUniversityDetail.ts b/src/apis/universities/getUniversityDetail.ts new file mode 100644 index 00000000..f5601ea6 --- /dev/null +++ b/src/apis/universities/getUniversityDetail.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, UniversityDetailResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetUniversityDetail = (univApplyInfoId: string | number, params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.universityDetail, univApplyInfoId, params], + queryFn: () => universitiesApi.getUniversityDetail({ univApplyInfoId, params }), + enabled: !!univApplyInfoId, + }); +}; + +export default useGetUniversityDetail; \ No newline at end of file diff --git a/src/apis/universities/getWishList.ts b/src/apis/universities/getWishList.ts new file mode 100644 index 00000000..5c9f238e --- /dev/null +++ b/src/apis/universities/getWishList.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { universitiesApi, WishListResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetWishList = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.universities.wishList, params], + queryFn: () => universitiesApi.getWishList(params ? { params } : {}), + }); +}; + +export default useGetWishList; \ No newline at end of file diff --git a/src/apis/universities/index.ts b/src/apis/universities/index.ts new file mode 100644 index 00000000..d7ddf2bb --- /dev/null +++ b/src/apis/universities/index.ts @@ -0,0 +1,10 @@ +export { universitiesApi } from './api'; +export { default as deleteWish } from './deleteWish'; +export { default as getByRegionCountry } from './getByRegionCountry'; +export { default as getIsWish } from './getIsWish'; +export { default as getRecommendedUniversities } from './getRecommendedUniversities'; +export { default as getSearchFilter } from './getSearchFilter'; +export { default as getSearchText } from './getSearchText'; +export { default as getUniversityDetail } from './getUniversityDetail'; +export { default as getWishList } from './getWishList'; +export { default as postAddWish } from './postAddWish'; diff --git a/src/apis/universities/postAddWish.ts b/src/apis/universities/postAddWish.ts new file mode 100644 index 00000000..92cf94e3 --- /dev/null +++ b/src/apis/universities/postAddWish.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { universitiesApi, AddWishResponse, AddWishRequest } from "./api"; + +const usePostAddWish = () => { + return useMutation({ + mutationFn: (variables) => universitiesApi.postAddWish(variables), + }); +}; + +export default usePostAddWish; \ No newline at end of file diff --git a/src/apis/users/api.ts b/src/apis/users/api.ts new file mode 100644 index 00000000..e9c4b789 --- /dev/null +++ b/src/apis/users/api.ts @@ -0,0 +1,54 @@ +import { axiosInstance } from "@/utils/axiosInstance"; + +export interface NicknameExistsResponse { + exists: boolean; +} + +export type BlockUserResponse = void; + +export type BlockUserRequest = Record; + +export type UnblockUserResponse = void; + +export interface BlockedUsersResponseContentItem { + id: number; + blockedId: number; + nickname: string; + createdAt: string; +} + +export interface BlockedUsersResponse { + content: BlockedUsersResponseContentItem[]; + nextPageNumber: number; +} + +export const usersApi = { + getNicknameExists: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/users/exists?nickname=abc`, { params: params?.params } + ); + return res.data; + }, + + postBlockUser: async (params: { blockedId: string | number, data?: BlockUserRequest }): Promise => { + const res = await axiosInstance.post( + `/users/block/${params.blockedId}`, params?.data + ); + return res.data; + }, + + deleteUnblockUser: async (params: { blockedId: string | number }): Promise => { + const res = await axiosInstance.delete( + `/users/block/${params.blockedId}` + ); + return res.data; + }, + + getBlockedUsers: async (params: { params?: Record }): Promise => { + const res = await axiosInstance.get( + `/users/blocks`, { params: params?.params } + ); + return res.data; + }, + +}; \ No newline at end of file diff --git a/src/apis/users/deleteUnblockUser.ts b/src/apis/users/deleteUnblockUser.ts new file mode 100644 index 00000000..385dd097 --- /dev/null +++ b/src/apis/users/deleteUnblockUser.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { usersApi, UnblockUserResponse, UnblockUserRequest } from "./api"; + +const useDeleteUnblockUser = () => { + return useMutation({ + mutationFn: (variables) => usersApi.deleteUnblockUser(variables), + }); +}; + +export default useDeleteUnblockUser; \ No newline at end of file diff --git a/src/apis/users/getBlockedUsers.ts b/src/apis/users/getBlockedUsers.ts new file mode 100644 index 00000000..1681e5dd --- /dev/null +++ b/src/apis/users/getBlockedUsers.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { usersApi, BlockedUsersResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetBlockedUsers = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.users.blockedUsers, params], + queryFn: () => usersApi.getBlockedUsers(params ? { params } : {}), + }); +}; + +export default useGetBlockedUsers; \ No newline at end of file diff --git a/src/apis/users/getNicknameExists.ts b/src/apis/users/getNicknameExists.ts new file mode 100644 index 00000000..7793059c --- /dev/null +++ b/src/apis/users/getNicknameExists.ts @@ -0,0 +1,13 @@ +import { AxiosError } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { usersApi, NicknameExistsResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; + +const useGetNicknameExists = (params?: Record) => { + return useQuery({ + queryKey: [QueryKeys.users.nicknameExists, params], + queryFn: () => usersApi.getNicknameExists(params ? { params } : {}), + }); +}; + +export default useGetNicknameExists; \ No newline at end of file diff --git a/src/apis/users/index.ts b/src/apis/users/index.ts new file mode 100644 index 00000000..6b491bd6 --- /dev/null +++ b/src/apis/users/index.ts @@ -0,0 +1,5 @@ +export { usersApi } from './api'; +export { default as deleteUnblockUser } from './deleteUnblockUser'; +export { default as getBlockedUsers } from './getBlockedUsers'; +export { default as getNicknameExists } from './getNicknameExists'; +export { default as postBlockUser } from './postBlockUser'; diff --git a/src/apis/users/postBlockUser.ts b/src/apis/users/postBlockUser.ts new file mode 100644 index 00000000..11b44fe8 --- /dev/null +++ b/src/apis/users/postBlockUser.ts @@ -0,0 +1,11 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { usersApi, BlockUserResponse, BlockUserRequest } from "./api"; + +const usePostBlockUser = () => { + return useMutation({ + mutationFn: (variables) => usersApi.postBlockUser(variables), + }); +}; + +export default usePostBlockUser; \ No newline at end of file From 25b039b96fa9376975ca3a947567dd0285df1f5d Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 18:23:41 +0900 Subject: [PATCH 02/11] refactor: migrate universities domain from api to apis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Migrate 10 hooks from api/university to apis/universities - useDeleteWish, useGetRecommendedUniversities, useGetWishList - useUniversitySearch, useGetUniversitySearchByFilter - useGetUniversityDetail, usePostAddWish, useGetByRegionCountry, useGetIsWish - Add server-side API functions in apis/universities/server - getRecommendedUniversity, getUniversityDetail - getSearchUniversitiesByText, getSearchUniversitiesByFilter - getAllUniversities, getCategorizedUniversities - Update QueryKeys: domain-specific → centralized (QueryKeys.universities.*) - Update component imports (8 files) - Add api-migration-prd.md documentation - Remove legacy api/university folder Breaking changes: None (internal refactoring) --- docs/api-migration-prd.md | 773 ++++++++++++++++++ src/api/university/client/queryKey.ts | 7 - .../client/useDeleteUniversityFavorite.ts | 23 - .../client/useGetMyWishUniversity.ts | 24 - .../client/useGetRecommendedUniversity.ts | 32 - .../client/useGetUniversityDetail.ts | 34 - .../client/useGetUniversitySearchByFilter.ts | 59 -- .../client/useGetUniversitySearchByText.ts | 69 -- .../client/usePostUniversityFavorite.ts | 51 -- src/apis/universities/api.ts | 25 +- src/apis/universities/deleteWish.ts | 18 +- .../getRecommendedUniversities.ts | 19 +- src/apis/universities/getSearchFilter.ts | 41 +- src/apis/universities/getSearchText.ts | 51 +- src/apis/universities/getUniversityDetail.ts | 16 +- src/apis/universities/getWishList.ts | 16 +- src/apis/universities/index.ts | 18 +- src/apis/universities/postAddWish.ts | 20 +- .../server/getRecommendedUniversity.ts | 0 .../server/getSearchUniversitiesByFilter.ts | 0 .../server/getSearchUniversitiesByText.ts | 0 .../server/getUniversityDetail.ts | 0 src/apis/universities/server/index.ts | 13 + src/app/(home)/page.tsx | 3 +- .../_components/UniversityScreen/index.tsx | 4 +- .../_hooks/useSelectUniversities.ts | 8 +- .../_hooks/useSortedUniversities.ts | 4 +- src/app/university/SearchResultsContent.tsx | 11 +- .../UniversityDetail/_ui/UniversityBtns.tsx | 10 +- src/app/university/[id]/page.tsx | 3 +- .../application/apply/ApplyPageContent.tsx | 9 +- 31 files changed, 982 insertions(+), 379 deletions(-) create mode 100644 docs/api-migration-prd.md delete mode 100644 src/api/university/client/queryKey.ts delete mode 100644 src/api/university/client/useDeleteUniversityFavorite.ts delete mode 100644 src/api/university/client/useGetMyWishUniversity.ts delete mode 100644 src/api/university/client/useGetRecommendedUniversity.ts delete mode 100644 src/api/university/client/useGetUniversityDetail.ts delete mode 100644 src/api/university/client/useGetUniversitySearchByFilter.ts delete mode 100644 src/api/university/client/useGetUniversitySearchByText.ts delete mode 100644 src/api/university/client/usePostUniversityFavorite.ts rename src/{api/university => apis/universities}/server/getRecommendedUniversity.ts (100%) rename src/{api/university => apis/universities}/server/getSearchUniversitiesByFilter.ts (100%) rename src/{api/university => apis/universities}/server/getSearchUniversitiesByText.ts (100%) rename src/{api/university => apis/universities}/server/getUniversityDetail.ts (100%) create mode 100644 src/apis/universities/server/index.ts diff --git a/docs/api-migration-prd.md b/docs/api-migration-prd.md new file mode 100644 index 00000000..38260a80 --- /dev/null +++ b/docs/api-migration-prd.md @@ -0,0 +1,773 @@ +# API 마이그레이션 PRD (Product Requirements Document) + +## api → apis 마이그레이션 프로젝트 + +--- + +## 1. 개요 + +### 1.1 목적 + +기존 수동 작성된 `src/api` 폴더의 코드를 자동 생성된 `src/apis` 폴더 기반으로 마이그레이션하여 API 규격을 자동화된 시스템으로 통일하고, 코드 일관성과 유지보수성을 향상시킵니다. + +### 1.2 배경 + +- 현재 API 클라이언트가 수동으로 작성되어 있어 일관성 부족 +- Bruno API 스펙에서 자동 생성된 `apis` 폴더가 이미 존재 +- API 스펙 변경 시 자동 동기화가 가능한 구조로 전환 필요 +- 비즈니스 로직과 API 호출 로직의 분리 필요 + +### 1.3 범위 + +- **대상**: `src/api` 폴더의 모든 도메인 (13개 도메인) +- **영향받는 파일**: 약 58개 컴포넌트/파일 +- **예상 기간**: 25-35시간 (도메인별 순차 진행) + +### 1.4 주요 변경사항 + +- Import 경로 변경: `@/api/{domain}/client/*` → `@/apis/{domain}/*` +- QueryKey 구조 변경: 도메인별 독립 → 중앙 집중식 관리 +- API 클라이언트: 수동 작성 → 자동 생성 기반 +- 비즈니스 로직: 기존 로직 완전 보존 (리다이렉트, 토스트, 상태 관리) + +--- + +## 2. 도메인별 마이그레이션 현황 + +### 2.1 도메인 목록 및 우선순위 + +| 우선순위 | 도메인 | 훅 수 | 컴포넌트 수 | 상태 | 예상 시간 | +| -------- | ------------- | ----- | ----------- | ------ | --------- | +| 1 | universities | 10 | 8 | ✅완료 | 2-3시간 | +| 2 | Auth | 8 | 10+ | 대기 | 3-4시간 | +| 3 | community | 9 | 15+ | 대기 | 3-4시간 | +| 4 | mentor/mentee | 10 | 20+ | 대기 | 4-5시간 | +| 5 | chat | 4 | 5+ | 대기 | 2-3시간 | +| 6 | news | 7 | 8+ | 대기 | 2-3시간 | +| 7 | Scores | 4 | 5+ | 대기 | 2시간 | +| 8 | MyPage | 3 | 5+ | 대기 | 2시간 | +| 9 | applications | 3 | 3+ | 대기 | 2시간 | +| 10 | reports | 1 | 2+ | 대기 | 1시간 | +| 11 | file | 1 | 2+ | 대기 | 1시간 | +| 12 | boards | 2 | 2+ | 대기 | 1시간 | +| 13 | mentors | 2 | 3+ | 대기 | 1시간 | + +**총계**: 약 61개 훅, 90+ 컴포넌트 + +### 2.2 진행 상태 추적 + +#### universities 도메인 ✅ 완료 + +- [x] 분석 완료 +- [x] API 클라이언트 수정 완료 +- [x] 훅 마이그레이션 완료 +- [x] 컴포넌트 업데이트 완료 +- [x] Server-side API 처리 완료 +- [x] 테스트 완료 +- [x] 커밋 완료 +- [x] 정리 완료 (레거시 폴더 삭제) + +#### 기타 도메인 (대기 중) + +- [ ] 분석 완료 +- [ ] API 클라이언트 수정 완료 +- [ ] 훅 마이그레이션 완료 +- [ ] 컴포넌트 업데이트 완료 +- [ ] Server-side API 처리 완료 +- [ ] 테스트 완료 +- [ ] 커밋 완료 +- [ ] 정리 완료 + +--- + +## 3. 주요 변경사항 + +### 3.1 Import 경로 변경 + +**Before:** + +```typescript +import { QueryKeys } from "@/api/university/client/queryKey"; +import useGetUniversityDetail from "@/api/university/client/useGetUniversityDetail"; +``` + +**After:** + +```typescript +import { QueryKeys } from "@/apis/queryKeys"; +import useGetUniversityDetail from "@/apis/universities/get-getUniversityDetail"; +``` + +**통계:** + +- 변경 예상 파일 수: 58개 +- Import 문 변경 수: 약 100+ 개 + +### 3.2 QueryKey 구조 변경 + +**Before (도메인별 독립):** + +```typescript +// api/university/client/queryKey.ts +export enum QueryKeys { + universityDetail = "universityDetail", + recommendedUniversity = "recommendedUniversity", +} +``` + +**After (중앙 집중식):** + +```typescript +// apis/queryKeys.ts +export const QueryKeys = { + universities: { + universityDetail: "universities.universityDetail", + recommendedUniversities: "universities.recommendedUniversities", + }, +}; +``` + +**변경 영향:** + +- QueryKey 사용처: 약 50+ 곳 +- `queryClient.invalidateQueries()` 호출: 약 30+ 곳 + +### 3.3 비즈니스 로직 보존 현황 + +다음 비즈니스 로직은 100% 보존됩니다: + +| 로직 유형 | 파일 수 | 보존 방법 | +| ------------------- | ------- | -------------------------------------- | +| 리다이렉트 로직 | 7 | `onSuccess`/`onError` 콜백에 유지 | +| 토스트 메시지 | 25 | 모든 토스트 호출 유지 | +| 상태 관리 (Zustand) | 10+ | `useAuthStore` 호출 유지 | +| 쿼리 무효화 | 30+ | `queryClient.invalidateQueries()` 유지 | +| 커스텀 다이얼로그 | 5+ | `customAlert`/`customConfirm` 유지 | + +### 3.4 타입 호환성 처리 + +**전략:** + +- 가능한 경우 `select` 옵션 사용하여 타입 변환 +- 필요한 경우 타입 어설션 최소화 사용 +- 기존 타입과 새 타입 매핑 테이블 작성 + +**예상 타입 불일치:** + +- 약 10-15개 훅에서 타입 매핑 필요 예상 + +--- + +## 4. 기술적 세부사항 + +### 4.1 API 클라이언트 수정 내역 + +**현재 문제:** + +- `apis/{domain}/api.ts`의 일부 URL이 `{`로 되어 있음 (자동 생성 미완료) + +**수정 방법:** + +- 기존 `api` 폴더의 엔드포인트 확인 +- `apis/api.ts`의 URL을 실제 엔드포인트로 수정 +- `publicAxiosInstance` vs `axiosInstance` 구분 확인 및 적용 + +**예시:** + +```typescript +// Before (apis/universities/api.ts) +getUniversityDetail: async (params) => { + const res = await axiosInstance.get(`{`, { params }); + return res.data; +}; + +// After +getUniversityDetail: async (universityInfoForApplyId: number) => { + const res = await publicAxiosInstance.get(`/univ-apply-infos/${universityInfoForApplyId}`); + return res.data; +}; +``` + +### 4.2 비즈니스 로직 마이그레이션 패턴 + +#### 패턴 1: 리다이렉트 로직 + +```typescript +// 기존 +const usePostKakaoAuth = () => { + return useMutation({ + mutationFn: postKakaoAuth, + onSuccess: (response) => { + if (data.isRegistered) { + router.replace(safeRedirect); + } else { + router.push(`/sign-up?token=${data.signUpToken}`); + } + }, + }); +}; + +// 새로운 (apis 래핑) +import { authApi } from "./api"; +const usePostKakaoAuth = () => { + return useMutation({ + mutationFn: (data) => authApi.postKakaoAuth({ data }), + onSuccess: (response) => { + // 기존 비즈니스 로직 100% 유지 + if (response.isRegistered) { + router.replace(safeRedirect); + } else { + router.push(`/sign-up?token=${response.signUpToken}`); + } + }, + }); +}; +``` + +#### 패턴 2: 토스트 + 쿼리 무효화 + +```typescript +// 새로운 +import { QueryKeys } from "../queryKeys"; +import { universitiesApi } from "./api"; + +// 기존 +const usePostUniversityFavorite = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: postUniversityFavoriteApi, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); + }, + onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), + }); +}; + +const usePostAddWish = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (id) => universitiesApi.postAddWish(id), + onSuccess: () => { + // QueryKey 구조 변경 반영 + queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); + }, + onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), + }); +}; +``` + +### 4.3 타입 매핑 전략 + +**전략 1: select 옵션 사용 (권장)** + +```typescript +useQuery({ + queryFn: () => universitiesApi.getUniversityDetail(id), + select: (data) => data as unknown as University, // 타입 변환 +}); +``` + +**전략 2: 타입 어설션 (최소화)** + +```typescript +const data = await universitiesApi.getWishList(); +return data as unknown as ListUniversity[]; +``` + +### 4.4 Server-side API 처리 방법 + +**현재 구조:** + +- `api/{domain}/server/*.ts`에서 `serverFetchUtil` 사용 +- ISR/SSR을 위한 공개 API 호출 + +**마이그레이션 방법:** + +```typescript +// 기존 +import serverFetch from "@/utils/serverFetchUtil"; +const getRecommendedUniversity = async () => { + const res = await serverFetch(endpoint); + return res; +}; + +// 새로운 (apis API를 serverFetchUtil로 래핑) +import { universitiesApi } from "@/apis/universities/api"; +import serverFetch from "@/utils/serverFetchUtil"; + +// 옵션 1: apis API 직접 사용 (클라이언트 전용) +// 옵션 2: serverFetchUtil 래퍼 생성 +const getRecommendedUniversity = async () => { + // serverFetchUtil 사용 유지 (ISR/SSR 호환) + const endpoint = "/univ-apply-infos/recommend"; + const res = await serverFetch(endpoint); + return res; +}; +``` + +**권장 접근:** + +- Server-side는 기존 `serverFetchUtil` 유지 +- Client-side만 `apis` 사용 +- 필요시 `apis` API를 `serverFetchUtil`로 래핑 + +--- + +## 5. 영향도 분석 + +### 5.1 변경된 파일 수 + +| 카테고리 | 파일 수 | +| ---------------- | -------------------- | +| API 훅 파일 | 61개 (마이그레이션) | +| 컴포넌트 파일 | 90+ 개 (import 변경) | +| QueryKey 파일 | 13개 (제거) | +| Server-side 파일 | 5개 (유지 또는 래핑) | +| **총계** | **170+ 개** | + +### 5.2 영향받는 주요 컴포넌트 + +**높은 영향도:** + +- `src/app/university/**` - 15+ 파일 +- `src/app/mentor/**` - 20+ 파일 +- `src/app/community/**` - 15+ 파일 +- `src/app/my/**` - 10+ 파일 +- `src/app/login/**` - 5+ 파일 + +**중간 영향도:** + +- `src/components/mentor/**` - 10+ 파일 +- `src/components/**` - 15+ 파일 + +### 5.3 잠재적 위험 요소 + +| 위험 요소 | 심각도 | 완화 전략 | +| --------------------------------------- | ------ | -------------------------------------- | +| 타입 불일치로 인한 런타임 오류 | 높음 | 철저한 타입 검증, `select` 옵션 활용 | +| 비즈니스 로직 누락 (리다이렉트, 토스트) | 높음 | 체크리스트 작성, 코드 리뷰 | +| QueryKey 변경으로 인한 캐시 문제 | 중간 | 모든 `invalidateQueries` 호출 검증 | +| Server-side 호환성 문제 | 중간 | ISR/SSR 테스트, `serverFetchUtil` 유지 | +| Import 경로 오류 | 낮음 | 자동화된 검색/교체, Linter 활용 | + +### 5.4 테스트 커버리지 + +**필수 테스트 항목:** + +- [ ] 각 도메인별 기능 테스트 +- [ ] 인증 플로우 테스트 (Auth 도메인) +- [ ] 리다이렉트 동작 확인 +- [ ] 토스트 메시지 표시 확인 +- [ ] 쿼리 캐시 무효화 확인 +- [ ] 타입 오류 확인 (TypeScript) +- [ ] 빌드 테스트 +- [ ] Linter 오류 확인 + +--- + +## 6. 마이그레이션 체크리스트 + +### 6.1 도메인별 완료 상태 + +각 도메인마다 다음 체크리스트를 완료해야 합니다: + +#### [도메인명] 마이그레이션 체크리스트 + +**1단계: 분석 및 준비** + +- [ ] 기존 도메인 폴더 구조 파악 +- [ ] `apis` 폴더의 해당 도메인 구조 확인 +- [ ] 비즈니스 로직 목록 작성 +- [ ] 사용처 파악 (import 찾기) +- [ ] QueryKey 매핑 테이블 작성 + +**2단계: API 클라이언트 수정** + +- [ ] `apis/{domain}/api.ts`의 URL 수정 +- [ ] 타입 정의 확인 및 매핑 +- [ ] `publicAxiosInstance` vs `axiosInstance` 구분 + +**3단계: 훅 마이그레이션** + +- [ ] 기존 훅의 비즈니스 로직 분석 +- [ ] `apis`의 기본 훅 확인 +- [ ] 비즈니스 로직을 포함한 래퍼 훅 생성 +- [ ] QueryKey 업데이트 +- [ ] 타입 호환성 확인 + +**4단계: 컴포넌트 업데이트** + +- [ ] 해당 도메인을 사용하는 모든 컴포넌트 찾기 +- [ ] Import 경로 변경 +- [ ] 타입 import 업데이트 +- [ ] QueryKey 사용처 업데이트 + +**5단계: Server-side API 처리** + +- [ ] `api/{domain}/server/*.ts` 파일 확인 +- [ ] `serverFetchUtil` 사용 여부 확인 +- [ ] ISR/SSR 호환성 유지 +- [ ] 필요시 래퍼 함수 생성 + +**6단계: 테스트 및 검증** + +- [ ] 기능 테스트 +- [ ] 타입 오류 확인 +- [ ] Linter 오류 확인 +- [ ] 빌드 테스트 + +**7단계: 커밋** + +- [ ] 모든 변경사항 스테이징 +- [ ] 커밋 메시지 작성 +- [ ] 커밋 완료 + +**8단계: 정리** + +- [ ] 기존 `api/{domain}` 폴더 제거 +- [ ] 사용하지 않는 import 정리 +- [ ] 문서 업데이트 (필요시) + +### 6.2 남은 작업 항목 + +**전체 진행률**: 0% (시작 전) + +**다음 작업:** + +1. universities 도메인 마이그레이션 시작 +2. 각 단계별 검증 +3. 진행 상황 기록 + +### 6.3 예상 완료 시점 + +- **시작일**: [작업 시작일] +- **예상 완료일**: [시작일 + 25-35시간] +- **도메인당 평균**: 2-4시간 + +--- + +## 7. 부록 + +### 7.1 도메인별 상세 변경 내역 + +각 도메인 마이그레이션 완료 후 다음 정보를 기록합니다: + +#### [도메인명] 상세 변경 내역 + +**마이그레이션된 훅:** + +- `useXXX` → `apis/{domain}/xxx-xxx` + +**변경된 컴포넌트:** + +- `src/app/...` (N개 파일) +- `src/components/...` (N개 파일) + +**QueryKey 변경:** + +- `QueryKeys.oldKey` → `QueryKeys.domain.newKey` + +**비즈니스 로직:** + +- 리다이렉트: N개 유지 +- 토스트: N개 유지 +- 상태 관리: N개 유지 + +**제거된 파일:** + +- `api/{domain}/client/*.ts` (N개) +- `api/{domain}/server/*.ts` (N개, 또는 유지) + +### 7.2 코드 예시 (Before/After) + +#### 예시 1: 단순 Query 훅 + +**Before:** + +```typescript +// api/university/client/useGetUniversityDetail.ts +import { publicAxiosInstance } from "@/utils/axiosInstance"; + +import { QueryKeys } from "./queryKey"; + +import { useQuery } from "@tanstack/react-query"; + +const getUniversityDetail = async (id: number) => { + const response = await publicAxiosInstance.get(`/univ-apply-infos/${id}`); + return response.data; +}; + +const useGetUniversityDetail = (id: number) => { + return useQuery({ + queryKey: [QueryKeys.universityDetail, id], + queryFn: () => getUniversityDetail(id), + enabled: !!id, + }); +}; +``` + +**After:** + +```typescript +// apis/universities/get-getUniversityDetail.ts +import { QueryKeys } from "../queryKeys"; +import { UniversityDetailResponse, universitiesApi } from "./api"; + +import { University } from "@/types/university"; + +import { useQuery } from "@tanstack/react-query"; + +const useGetUniversityDetail = (id: number) => { + return useQuery({ + queryKey: [QueryKeys.universities.universityDetail, id], + queryFn: () => universitiesApi.getUniversityDetail(id), + enabled: !!id, + select: (data) => data as unknown as University, + }); +}; +``` + +#### 예시 2: 비즈니스 로직 포함 Mutation 훅 + +**Before:** + +```typescript +// api/auth/client/usePostKakaoAuth.ts +const usePostKakaoAuth = () => { + const { setAccessToken } = useAuthStore(); + const router = useRouter(); + const searchParams = useSearchParams(); + + return useMutation({ + mutationFn: postKakaoAuth, + onSuccess: (response) => { + const { data } = response; + if (data.isRegistered) { + setAccessToken(data.accessToken); + const redirectParam = searchParams.get("redirect"); + const safeRedirect = validateSafeRedirect(redirectParam); + toast.success("로그인에 성공했습니다."); + router.replace(safeRedirect); + } else { + router.push(`/sign-up?token=${data.signUpToken}`); + } + }, + onError: () => { + toast.error("카카오 로그인 중 오류가 발생했습니다."); + router.push("/login"); + }, + }); +}; +``` + +**After:** + +```typescript +// apis/Auth/post-postKakaoAuth.ts (래퍼 훅) +import { useRouter, useSearchParams } from "next/navigation"; + +import { validateSafeRedirect } from "@/utils/authUtils"; + +import { KakaoAuthResponse, authApi } from "./api"; + +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; + +const usePostKakaoAuth = () => { + const { setAccessToken } = useAuthStore(); + const router = useRouter(); + const searchParams = useSearchParams(); + + return useMutation({ + mutationFn: (data) => authApi.postKakaoAuth({ data }), + onSuccess: (response) => { + // 기존 비즈니스 로직 100% 유지 + if (response.isRegistered) { + setAccessToken(response.accessToken); + const redirectParam = searchParams.get("redirect"); + const safeRedirect = validateSafeRedirect(redirectParam); + toast.success("로그인에 성공했습니다."); + router.replace(safeRedirect); + } else { + router.push(`/sign-up?token=${response.signUpToken}`); + } + }, + onError: () => { + toast.error("카카오 로그인 중 오류가 발생했습니다."); + router.push("/login"); + }, + }); +}; +``` + +### 7.3 마이그레이션 가이드 + +#### 단계별 가이드 + +**1. 도메인 선택 및 분석** + +```bash +# 1. 도메인 폴더 확인 +ls src/api/{domain}/client/ + +# 2. 사용처 찾기 +grep -r "from.*@/api/{domain}" src/ + +# 3. apis 폴더 확인 +ls src/apis/{domain}/ +``` + +**2. API 클라이언트 수정** + +- `apis/{domain}/api.ts` 파일 열기 +- URL이 `{`로 되어 있으면 기존 `api` 폴더의 엔드포인트 확인 +- `publicAxiosInstance` vs `axiosInstance` 구분 확인 + +**3. 훅 마이그레이션** + +- 기존 훅 파일 열기 +- 비즈니스 로직 파악 (리다이렉트, 토스트, 상태 관리) +- `apis`의 기본 훅 확인 +- 비즈니스 로직을 포함한 래퍼 훅 작성 + +**4. 컴포넌트 업데이트** + +```bash +# Import 경로 일괄 변경 (주의: 수동 검증 필요) +find src/ -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i '' \ + 's|@/api/{domain}/client/|@/apis/{domain}/|g' +``` + +**5. QueryKey 업데이트** + +- `apis/queryKeys.ts`에서 해당 도메인 키 확인 +- 모든 사용처에서 `QueryKeys.old` → `QueryKeys.domain.new` 변경 + +**6. 테스트** + +```bash +# 타입 체크 +npm run type-check + +# Linter +npm run lint + +# 빌드 +npm run build +``` + +**7. 커밋** + +```bash +git add . +git commit -m "refactor: migrate {domain} from api to apis + +Changes: +- Migrate {N} hooks from api/{domain} to apis/{domain} +- Preserve business logic (redirects, toasts, state management) +- Update QueryKeys: {old} → {new} +- Update {N} component imports +- Remove api/{domain} folder" +``` + +### 7.4 커밋 메시지 템플릿 + +``` +refactor: migrate {domain} from api to apis + +Changes: +- Migrate {N} hooks from api/{domain} to apis/{domain} +- Preserve business logic (redirects, toasts, state management) +- Update QueryKeys: {old structure} → {new structure} +- Update {N} component imports +- Remove api/{domain} folder + +Files changed: +- src/apis/{domain}/* (modified/created) +- src/app/**/* (imports updated) +- src/components/**/* (imports updated) +- src/api/{domain}/* (removed) + +Breaking changes: None (internal refactoring) +``` + +### 7.5 비즈니스 로직 체크리스트 + +각 훅 마이그레이션 시 다음 항목을 확인: + +- [ ] 리다이렉트 로직 유지 (`router.push`, `router.replace`) +- [ ] 토스트 메시지 유지 (`toast.success`, `toast.error`) +- [ ] 상태 관리 유지 (`useAuthStore`, `setAccessToken`, `clearAccessToken`) +- [ ] 쿼리 무효화 유지 (`queryClient.invalidateQueries`) +- [ ] 커스텀 다이얼로그 유지 (`customAlert`, `customConfirm`) +- [ ] 에러 핸들링 유지 (`onError` 콜백) +- [ ] 조건부 로직 유지 (`if/else` 분기) + +--- + +## 8. 리스크 관리 + +### 8.1 주요 리스크 및 완화 전략 + +| 리스크 | 심각도 | 확률 | 완화 전략 | +| -------------------------------- | ------ | ---- | ----------------------------------------------------------- | +| 타입 불일치로 인한 런타임 오류 | 높음 | 중간 | 철저한 타입 검증, `select` 옵션 활용, 타입 매핑 테이블 작성 | +| 비즈니스 로직 누락 | 높음 | 낮음 | 체크리스트 작성, 코드 리뷰, 단계별 검증 | +| QueryKey 변경으로 인한 캐시 문제 | 중간 | 낮음 | 모든 `invalidateQueries` 호출 검증, 테스트 | +| Server-side 호환성 문제 | 중간 | 낮음 | ISR/SSR 테스트, `serverFetchUtil` 유지 | +| Import 경로 오류 | 낮음 | 낮음 | 자동화된 검색/교체, Linter 활용 | + +### 8.2 롤백 계획 + +각 도메인 마이그레이션 후 문제 발생 시: + +1. 해당 도메인의 커밋만 롤백 +2. 기존 `api/{domain}` 폴더 복원 +3. Import 경로 복원 +4. 문제 분석 후 재시도 + +--- + +## 9. 성공 기준 + +### 9.1 기능적 요구사항 + +- [ ] 모든 도메인 마이그레이션 완료 +- [ ] 기존 기능 100% 동작 보장 +- [ ] 비즈니스 로직 100% 보존 +- [ ] 타입 오류 0개 +- [ ] Linter 오류 0개 +- [ ] 빌드 성공 + +### 9.2 비기능적 요구사항 + +- [ ] 코드 일관성 향상 +- [ ] 유지보수성 향상 +- [ ] API 스펙 자동 동기화 가능 +- [ ] 성능 저하 없음 + +--- + +## 10. 참고 자료 + +### 10.1 관련 문서 + +- [마이그레이션 계획서](./api_마이그레이션_통합_계획.md) +- [온보딩 문서](./onboarding.md) +- [Git/CICD 가이드](./git-cicd.md) + +### 10.2 관련 파일 + +- `src/api/` - 기존 API 폴더 +- `src/apis/` - 새로운 API 폴더 (자동 생성) +- `src/apis/queryKeys.ts` - 중앙 집중식 QueryKey +- `src/utils/axiosInstance.ts` - Axios 인스턴스 +- `src/utils/serverFetchUtil.ts` - Server-side fetch 유틸 + +--- + +**문서 버전**: 1.0 +**작성일**: [작성일] +**최종 수정일**: [수정일] +**작성자**: Development Team diff --git a/src/api/university/client/queryKey.ts b/src/api/university/client/queryKey.ts deleted file mode 100644 index f71a2587..00000000 --- a/src/api/university/client/queryKey.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum QueryKeys { - recommendedUniversity = "recommendedUniversity", - univApplyInfosLike = "univApplyInfosLike", - universitySearchText = "universitySearchText", - universitySearchFilter = "universitySearchFilter", - universityDetail = "universityDetail", -} diff --git a/src/api/university/client/useDeleteUniversityFavorite.ts b/src/api/university/client/useDeleteUniversityFavorite.ts deleted file mode 100644 index 9413afa2..00000000 --- a/src/api/university/client/useDeleteUniversityFavorite.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -const deleteUniversityFavorite = (universityInfoForApplyId: number): Promise => - axiosInstance.delete(`/univ-apply-infos/${universityInfoForApplyId}/like`); - -const useDeleteUniversityFavorite = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: deleteUniversityFavorite, - onSuccess: () => { - // 위시리스트 관련 쿼리를 무효화하여 데이터를 다시 불러옵니다. - queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); - }, - }); -}; - -export default useDeleteUniversityFavorite; diff --git a/src/api/university/client/useGetMyWishUniversity.ts b/src/api/university/client/useGetMyWishUniversity.ts deleted file mode 100644 index 0b2c74cc..00000000 --- a/src/api/university/client/useGetMyWishUniversity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ListUniversity } from "@/types/university"; - -import { useQuery } from "@tanstack/react-query"; - -export const getMyWishUniversity = (): Promise> => - axiosInstance.get("/univ-apply-infos/like"); - -const useGetMyWishUniversity = (enabled: boolean = true) => { - return useQuery({ - queryKey: [QueryKeys.univApplyInfosLike], - queryFn: () => getMyWishUniversity(), - staleTime: 1000 * 60 * 5, - select: (data) => data.data, - enabled, - }); -}; - -export default useGetMyWishUniversity; diff --git a/src/api/university/client/useGetRecommendedUniversity.ts b/src/api/university/client/useGetRecommendedUniversity.ts deleted file mode 100644 index f765129d..00000000 --- a/src/api/university/client/useGetRecommendedUniversity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ListUniversity } from "@/types/university"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { useQuery } from "@tanstack/react-query"; - -type UseGetRecommendedUniversityResponse = { recommendedUniversities: ListUniversity[] }; -type UseGetRecommendedUniversityRequest = { - isLogin: boolean; -}; - -const getRecommendedUniversity = async (isLogin: boolean): Promise => { - const endpoint = "/univ-apply-infos/recommend"; - - const accessToken = useAuthStore.getState().accessToken; - const instance = isLogin && accessToken ? axiosInstance : publicAxiosInstance; - const res = await instance.get(endpoint); - return res.data; -}; - -const useGetRecommendedUniversity = ({ isLogin }: UseGetRecommendedUniversityRequest) => - useQuery({ - queryKey: [QueryKeys.recommendedUniversity, isLogin], - queryFn: () => getRecommendedUniversity(isLogin), - staleTime: 1000 * 60 * 5, - select: (data) => data.recommendedUniversities, // 필요한 데이터만 반환 - }); - -export default useGetRecommendedUniversity; diff --git a/src/api/university/client/useGetUniversityDetail.ts b/src/api/university/client/useGetUniversityDetail.ts deleted file mode 100644 index 57faab21..00000000 --- a/src/api/university/client/useGetUniversityDetail.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { University } from "@/types/university"; - -import { useQuery } from "@tanstack/react-query"; - -/** - * @description 대학 상세 조회 API 함수 (공개) - * @param universityInfoForApplyId - 대학 ID - * @returns Promise - */ -const getUniversityDetail = async (universityInfoForApplyId: number): Promise => { - const response: AxiosResponse = await publicAxiosInstance.get( - `/univ-apply-infos/${universityInfoForApplyId}`, - ); - return response.data; -}; - -/** - * @description 대학 상세 조회를 위한 useQuery 커스텀 훅 - */ -const useGetUniversityDetail = (universityInfoForApplyId: number) => { - return useQuery({ - queryKey: [QueryKeys.universityDetail, universityInfoForApplyId], - queryFn: () => getUniversityDetail(universityInfoForApplyId), - enabled: !!universityInfoForApplyId, - }); -}; - -export default useGetUniversityDetail; diff --git a/src/api/university/client/useGetUniversitySearchByFilter.ts b/src/api/university/client/useGetUniversitySearchByFilter.ts deleted file mode 100644 index 67dbfbb1..00000000 --- a/src/api/university/client/useGetUniversitySearchByFilter.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { CountryCode, LanguageTestType, ListUniversity } from "@/types/university"; - -import { useQuery } from "@tanstack/react-query"; - -// --- 타입 정의 --- -export interface UniversitySearchFilterParams { - languageTestType?: LanguageTestType; - testScore?: number; - countryCode?: CountryCode[]; -} - -interface UniversitySearchFilterResponse { - univApplyInfoPreviews: ListUniversity[]; -} - -// --- API 호출 함수 --- -const getUniversitySearchByFilter = async ( - filters: UniversitySearchFilterParams, -): Promise => { - const params = new URLSearchParams(); - - if (filters.languageTestType) { - params.append("languageTestType", filters.languageTestType); - } - if (filters.testScore !== undefined) { - params.append("testScore", String(filters.testScore)); - } - if (filters.countryCode) { - filters.countryCode.forEach((code) => { - params.append("countryCode", code); - }); - } - - const response: AxiosResponse = await publicAxiosInstance.get( - `/univ-apply-infos/search/filter?${params.toString()}`, - ); - return response.data; -}; - -// --- 커스텀 훅 --- -const useGetUniversitySearchByFilter = (filters: UniversitySearchFilterParams) => { - return useQuery({ - queryKey: [QueryKeys.universitySearchFilter, filters], - queryFn: () => getUniversitySearchByFilter(filters), - enabled: Object.values(filters).some((value) => { - if (Array.isArray(value)) return value.length > 0; - return value !== undefined && value !== ""; - }), - select: (data) => data.univApplyInfoPreviews, - }); -}; - -export default useGetUniversitySearchByFilter; diff --git a/src/api/university/client/useGetUniversitySearchByText.ts b/src/api/university/client/useGetUniversitySearchByText.ts deleted file mode 100644 index f63c0579..00000000 --- a/src/api/university/client/useGetUniversitySearchByText.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useMemo } from "react"; - -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ListUniversity } from "@/types/university"; - -import { useQuery } from "@tanstack/react-query"; - -// --- 타입 정의 --- -interface UniversitySearchTextResponse { - univApplyInfoPreviews: ListUniversity[]; -} - -// --- API 호출 함수 --- -const getAllUniversitiesApi = async (): Promise => { - const response: AxiosResponse = await publicAxiosInstance.get( - "/univ-apply-infos/search/text", - { - params: { value: "" }, // 항상 빈 값으로 호출 - }, - ); - return response.data; -}; - -const useUniversitySearch = (searchValue: string) => { - // 1. 모든 대학 데이터를 한 번만 가져와 'Infinity' 캐시로 저장합니다. - const { - data: allUniversities, // 모든 대학 목록 - isLoading, - isError, - error, - } = useQuery({ - queryKey: [QueryKeys.universitySearchText], // "모든 대학"을 위한 고유 키 - queryFn: getAllUniversitiesApi, - staleTime: Infinity, // 한번 가져오면 절대 다시 요청하지 않음 - gcTime: Infinity, // 캐시가 절대 삭제되지 않음 (선택 사항) - select: (data) => data.univApplyInfoPreviews, - }); - - // 2. 검색어가 변경될 때만 캐시된 데이터를 필터링합니다. - const filteredUniversities = useMemo(() => { - const normalizedSearchValue = searchValue.trim().toLowerCase(); - - if (!normalizedSearchValue) { - return allUniversities; // 검색어가 없으면 전체 목록 반환 - } - - // allUniversities가 아직 로드되지 않았으면 빈 배열 반환 - if (!allUniversities) { - return []; - } - - // 대학 이름(koreanName)에 검색어가 포함되어 있는지 확인하여 필터링 - return allUniversities.filter((university) => university.koreanName.toLowerCase().includes(normalizedSearchValue)); - }, [allUniversities, searchValue]); // allUniversities나 searchValue가 바뀔 때만 재계산 - - return { - data: filteredUniversities, // 필터링된 결과 - isLoading, // 초기 데이터 로딩 상태 - isError, - error, - totalCount: allUniversities?.length || 0, // 전체 대학 수 (필요시 사용) - }; -}; -export default useUniversitySearch; diff --git a/src/api/university/client/usePostUniversityFavorite.ts b/src/api/university/client/usePostUniversityFavorite.ts deleted file mode 100644 index 106cbb10..00000000 --- a/src/api/university/client/usePostUniversityFavorite.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; -import { createMutationErrorHandler } from "@/utils/errorHandler"; - -import { QueryKeys } from "./queryKey"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 위시리스트 학교 추가 API 응답 타입 - * @property {number} universityInfoForApplyId - 추가된 학교 정보 ID - * @property {string} message - 성공 메시지 - */ -interface UniversityFavoriteResponse { - universityInfoForApplyId: number; - message: string; -} - -/** - * @description 위시리스트에 학교를 추가하는 API 함수 - * @param universityInfoForApplyId - 추가할 학교 정보의 ID - * @returns Promise> - */ -export const postUniversityFavoriteApi = ( - universityInfoForApplyId: number, -): Promise> => - axiosInstance.post(`/univ-apply-infos/${universityInfoForApplyId}/like`); - -/** - * @description 위시리스트 학교 추가를 위한 useMutation 커스텀 훅 - */ -const usePostUniversityFavorite = () => { - const queryClient = useQueryClient(); - - return useMutation({ - // mutation 실행 시 호출될 함수 - mutationFn: postUniversityFavoriteApi, - - // mutation 성공 시 실행될 콜백 - onSuccess: () => { - // 위시리스트 관련 쿼리를 무효화하여 데이터를 다시 불러옵니다. - queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); - }, - - // mutation 실패 시 실행될 콜백 - onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), - }); -}; - -export default usePostUniversityFavorite; diff --git a/src/apis/universities/api.ts b/src/apis/universities/api.ts index c11d4141..0dccab72 100644 --- a/src/apis/universities/api.ts +++ b/src/apis/universities/api.ts @@ -1,4 +1,4 @@ -import { axiosInstance } from "@/utils/axiosInstance"; +import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; export interface RecommendedUniversitiesResponseRecommendedUniversitiesItem { id: number; @@ -130,9 +130,10 @@ export interface SearchFilterResponse { export type ByRegionCountryResponse = void; export const universitiesApi = { - getRecommendedUniversities: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/univ-apply-infos/recommend`, { params: params?.params } + getRecommendedUniversities: async (params?: { isLogin?: boolean }): Promise => { + const instance = params?.isLogin ? axiosInstance : publicAxiosInstance; + const res = await instance.get( + `/univ-apply-infos/recommend` ); return res.data; }, @@ -165,22 +166,22 @@ export const universitiesApi = { return res.data; }, - getUniversityDetail: async (params: { univApplyInfoId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/univ-apply-infos/${params.univApplyInfoId}`, { params: params?.params } + getUniversityDetail: async (params: { univApplyInfoId: string | number }): Promise => { + const res = await publicAxiosInstance.get( + `/univ-apply-infos/${params.univApplyInfoId}` ); return res.data; }, - getSearchText: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/univ-apply-infos/search/text?value=괌`, { params: params?.params } + getSearchText: async (params?: { value?: string }): Promise => { + const res = await publicAxiosInstance.get( + `/univ-apply-infos/search/text`, { params: { value: params?.value ?? "" } } ); return res.data; }, - getSearchFilter: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( + getSearchFilter: async (params?: { params?: Record }): Promise => { + const res = await publicAxiosInstance.get( `/univ-apply-infos/search/filter`, { params: params?.params } ); return res.data; diff --git a/src/apis/universities/deleteWish.ts b/src/apis/universities/deleteWish.ts index e4c88b1f..f6c015c3 100644 --- a/src/apis/universities/deleteWish.ts +++ b/src/apis/universities/deleteWish.ts @@ -1,10 +1,20 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { universitiesApi, WishResponse, WishRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { universitiesApi, WishResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; +/** + * @description 위시리스트에서 학교를 삭제하는 useMutation 커스텀 훅 + */ const useDeleteWish = () => { - return useMutation({ - mutationFn: (variables) => universitiesApi.deleteWish(variables), + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (universityInfoForApplyId) => + universitiesApi.deleteWish({ univApplyInfoId: universityInfoForApplyId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); + }, }); }; diff --git a/src/apis/universities/getRecommendedUniversities.ts b/src/apis/universities/getRecommendedUniversities.ts index 014e50e7..8137bd6c 100644 --- a/src/apis/universities/getRecommendedUniversities.ts +++ b/src/apis/universities/getRecommendedUniversities.ts @@ -2,11 +2,22 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { universitiesApi, RecommendedUniversitiesResponse } from "./api"; import { QueryKeys } from "../queryKeys"; +import { ListUniversity } from "@/types/university"; -const useGetRecommendedUniversities = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.universities.recommendedUniversities, params], - queryFn: () => universitiesApi.getRecommendedUniversities(params ? { params } : {}), +type UseGetRecommendedUniversitiesParams = { + isLogin: boolean; +}; + +/** + * @description 추천 대학 목록 조회를 위한 useQuery 커스텀 훅 + * @param params.isLogin - 로그인 여부 (인스턴스 결정에 사용) + */ +const useGetRecommendedUniversities = ({ isLogin }: UseGetRecommendedUniversitiesParams) => { + return useQuery({ + queryKey: [QueryKeys.universities.recommendedUniversities, isLogin], + queryFn: () => universitiesApi.getRecommendedUniversities({ isLogin }), + staleTime: 1000 * 60 * 5, + select: (data) => data.recommendedUniversities as unknown as ListUniversity[], }); }; diff --git a/src/apis/universities/getSearchFilter.ts b/src/apis/universities/getSearchFilter.ts index 34befa8e..844f9fcc 100644 --- a/src/apis/universities/getSearchFilter.ts +++ b/src/apis/universities/getSearchFilter.ts @@ -2,12 +2,43 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { universitiesApi, SearchFilterResponse } from "./api"; import { QueryKeys } from "../queryKeys"; +import { CountryCode, LanguageTestType, ListUniversity } from "@/types/university"; -const useGetSearchFilter = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.universities.searchFilter, params], - queryFn: () => universitiesApi.getSearchFilter(params ? { params } : {}), +export interface UniversitySearchFilterParams { + languageTestType?: LanguageTestType; + testScore?: number; + countryCode?: CountryCode[]; +} + +/** + * @description 필터로 대학 검색을 위한 useQuery 커스텀 훅 + * @param filters - 검색 필터 파라미터 + */ +const useGetUniversitySearchByFilter = (filters: UniversitySearchFilterParams) => { + // 필터 파라미터 구성 + const buildParams = () => { + const params: Record = {}; + if (filters.languageTestType) { + params.languageTestType = filters.languageTestType; + } + if (filters.testScore !== undefined) { + params.testScore = String(filters.testScore); + } + if (filters.countryCode && filters.countryCode.length > 0) { + params.countryCode = filters.countryCode; + } + return params; + }; + + return useQuery({ + queryKey: [QueryKeys.universities.searchFilter, filters], + queryFn: () => universitiesApi.getSearchFilter({ params: buildParams() }), + enabled: Object.values(filters).some((value) => { + if (Array.isArray(value)) return value.length > 0; + return value !== undefined && value !== ""; + }), + select: (data) => data.univApplyInfoPreviews as unknown as ListUniversity[], }); }; -export default useGetSearchFilter; \ No newline at end of file +export default useGetUniversitySearchByFilter; \ No newline at end of file diff --git a/src/apis/universities/getSearchText.ts b/src/apis/universities/getSearchText.ts index 30967c5e..a322eec1 100644 --- a/src/apis/universities/getSearchText.ts +++ b/src/apis/universities/getSearchText.ts @@ -1,13 +1,54 @@ +import { useMemo } from "react"; import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { universitiesApi, SearchTextResponse } from "./api"; import { QueryKeys } from "../queryKeys"; +import { ListUniversity } from "@/types/university"; -const useGetSearchText = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.universities.searchText, params], - queryFn: () => universitiesApi.getSearchText(params ? { params } : {}), +/** + * @description 대학 검색을 위한 useQuery 커스텀 훅 + * 모든 대학 데이터를 한 번만 가져와 캐싱하고, 검색어에 따라 클라이언트에서 필터링합니다. + * @param searchValue - 검색어 + */ +const useUniversitySearch = (searchValue: string) => { + // 1. 모든 대학 데이터를 한 번만 가져와 'Infinity' 캐시로 저장합니다. + const { + data: allUniversities, + isLoading, + isError, + error, + } = useQuery({ + queryKey: [QueryKeys.universities.searchText], + queryFn: () => universitiesApi.getSearchText({ value: "" }), + staleTime: Infinity, + gcTime: Infinity, + select: (data) => data.univApplyInfoPreviews as unknown as ListUniversity[], }); + + // 2. 검색어가 변경될 때만 캐시된 데이터를 필터링합니다. + const filteredUniversities = useMemo(() => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + + if (!normalizedSearchValue) { + return allUniversities; + } + + if (!allUniversities) { + return []; + } + + return allUniversities.filter((university) => + university.koreanName.toLowerCase().includes(normalizedSearchValue) + ); + }, [allUniversities, searchValue]); + + return { + data: filteredUniversities, + isLoading, + isError, + error, + totalCount: allUniversities?.length || 0, + }; }; -export default useGetSearchText; \ No newline at end of file +export default useUniversitySearch; \ No newline at end of file diff --git a/src/apis/universities/getUniversityDetail.ts b/src/apis/universities/getUniversityDetail.ts index f5601ea6..f6e93c9a 100644 --- a/src/apis/universities/getUniversityDetail.ts +++ b/src/apis/universities/getUniversityDetail.ts @@ -2,12 +2,18 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { universitiesApi, UniversityDetailResponse } from "./api"; import { QueryKeys } from "../queryKeys"; +import { University } from "@/types/university"; -const useGetUniversityDetail = (univApplyInfoId: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.universities.universityDetail, univApplyInfoId, params], - queryFn: () => universitiesApi.getUniversityDetail({ univApplyInfoId, params }), - enabled: !!univApplyInfoId, +/** + * @description 대학 상세 조회를 위한 useQuery 커스텀 훅 + * @param universityInfoForApplyId - 대학 ID + */ +const useGetUniversityDetail = (universityInfoForApplyId: number) => { + return useQuery({ + queryKey: [QueryKeys.universities.universityDetail, universityInfoForApplyId], + queryFn: () => universitiesApi.getUniversityDetail({ univApplyInfoId: universityInfoForApplyId }), + enabled: !!universityInfoForApplyId, + select: (data) => data as unknown as University, }); }; diff --git a/src/apis/universities/getWishList.ts b/src/apis/universities/getWishList.ts index 5c9f238e..e0b406f1 100644 --- a/src/apis/universities/getWishList.ts +++ b/src/apis/universities/getWishList.ts @@ -2,11 +2,19 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; import { universitiesApi, WishListResponse } from "./api"; import { QueryKeys } from "../queryKeys"; +import { ListUniversity } from "@/types/university"; -const useGetWishList = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.universities.wishList, params], - queryFn: () => universitiesApi.getWishList(params ? { params } : {}), +/** + * @description 내 위시리스트 대학 목록 조회를 위한 useQuery 커스텀 훅 + * @param enabled - 쿼리 활성화 여부 + */ +const useGetWishList = (enabled: boolean = true) => { + return useQuery({ + queryKey: [QueryKeys.universities.wishList], + queryFn: () => universitiesApi.getWishList({}), + staleTime: 1000 * 60 * 5, + select: (data) => data as unknown as ListUniversity[], + enabled, }); }; diff --git a/src/apis/universities/index.ts b/src/apis/universities/index.ts index d7ddf2bb..75eac64d 100644 --- a/src/apis/universities/index.ts +++ b/src/apis/universities/index.ts @@ -1,10 +1,10 @@ export { universitiesApi } from './api'; -export { default as deleteWish } from './deleteWish'; -export { default as getByRegionCountry } from './getByRegionCountry'; -export { default as getIsWish } from './getIsWish'; -export { default as getRecommendedUniversities } from './getRecommendedUniversities'; -export { default as getSearchFilter } from './getSearchFilter'; -export { default as getSearchText } from './getSearchText'; -export { default as getUniversityDetail } from './getUniversityDetail'; -export { default as getWishList } from './getWishList'; -export { default as postAddWish } from './postAddWish'; +export { default as useDeleteWish } from './deleteWish'; +export { default as useGetByRegionCountry } from './getByRegionCountry'; +export { default as useGetIsWish } from './getIsWish'; +export { default as useGetRecommendedUniversities } from './getRecommendedUniversities'; +export { default as useGetUniversitySearchByFilter, type UniversitySearchFilterParams } from './getSearchFilter'; +export { default as useUniversitySearch } from './getSearchText'; +export { default as useGetUniversityDetail } from './getUniversityDetail'; +export { default as useGetWishList } from './getWishList'; +export { default as usePostAddWish } from './postAddWish'; diff --git a/src/apis/universities/postAddWish.ts b/src/apis/universities/postAddWish.ts index 92cf94e3..49e5e999 100644 --- a/src/apis/universities/postAddWish.ts +++ b/src/apis/universities/postAddWish.ts @@ -1,10 +1,22 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { universitiesApi, AddWishResponse, AddWishRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { universitiesApi, AddWishResponse } from "./api"; +import { QueryKeys } from "../queryKeys"; +import { createMutationErrorHandler } from "@/utils/errorHandler"; +/** + * @description 위시리스트에 학교를 추가하는 useMutation 커스텀 훅 + */ const usePostAddWish = () => { - return useMutation({ - mutationFn: (variables) => universitiesApi.postAddWish(variables), + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (universityInfoForApplyId) => + universitiesApi.postAddWish({ univApplyInfoId: universityInfoForApplyId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); + }, + onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), }); }; diff --git a/src/api/university/server/getRecommendedUniversity.ts b/src/apis/universities/server/getRecommendedUniversity.ts similarity index 100% rename from src/api/university/server/getRecommendedUniversity.ts rename to src/apis/universities/server/getRecommendedUniversity.ts diff --git a/src/api/university/server/getSearchUniversitiesByFilter.ts b/src/apis/universities/server/getSearchUniversitiesByFilter.ts similarity index 100% rename from src/api/university/server/getSearchUniversitiesByFilter.ts rename to src/apis/universities/server/getSearchUniversitiesByFilter.ts diff --git a/src/api/university/server/getSearchUniversitiesByText.ts b/src/apis/universities/server/getSearchUniversitiesByText.ts similarity index 100% rename from src/api/university/server/getSearchUniversitiesByText.ts rename to src/apis/universities/server/getSearchUniversitiesByText.ts diff --git a/src/api/university/server/getUniversityDetail.ts b/src/apis/universities/server/getUniversityDetail.ts similarity index 100% rename from src/api/university/server/getUniversityDetail.ts rename to src/apis/universities/server/getUniversityDetail.ts diff --git a/src/apis/universities/server/index.ts b/src/apis/universities/server/index.ts new file mode 100644 index 00000000..9a1db871 --- /dev/null +++ b/src/apis/universities/server/index.ts @@ -0,0 +1,13 @@ +// Server-side exports +export { default as getRecommendedUniversity } from "./getRecommendedUniversity"; +export { getUniversityDetail } from "./getUniversityDetail"; +export { + getUniversitiesByText, + getAllUniversities, + getCategorizedUniversities, +} from "./getSearchUniversitiesByText"; +export { + getSearchUniversitiesByFilter, + getSearchUniversitiesAllRegions, + type UniversitySearchFilterParams, +} from "./getSearchUniversitiesByFilter"; diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 1cba4994..8295fa87 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -7,8 +7,7 @@ import NewsSectionSkeleton from "./_ui/NewsSection/skeleton"; import PopularUniversitySection from "./_ui/PopularUniversitySection"; import UniversityList from "./_ui/UniversityList"; -import getRecommendedUniversity from "@/api/university/server/getRecommendedUniversity"; -import { getCategorizedUniversities } from "@/api/university/server/getSearchUniversitiesByText"; +import { getRecommendedUniversity, getCategorizedUniversities } from "@/apis/universities/server"; import { fetchAllNews } from "@/lib/firebaseNews"; import { IconIdCard, IconMagnifyingGlass, IconMuseum, IconPaper } from "@/public/svgs/home"; diff --git a/src/app/my/apply-mentor/_components/UniversityScreen/index.tsx b/src/app/my/apply-mentor/_components/UniversityScreen/index.tsx index 03b46131..46ed8c2f 100644 --- a/src/app/my/apply-mentor/_components/UniversityScreen/index.tsx +++ b/src/app/my/apply-mentor/_components/UniversityScreen/index.tsx @@ -11,7 +11,7 @@ import { MentorApplicationFormData } from "../../_lib/schema"; import { mentorRegionList } from "@/constants/regions"; -import useGetUniversitySearchByText from "@/api/university/client/useGetUniversitySearchByText"; +import { useUniversitySearch } from "@/apis/universities"; import { toast } from "@/lib/zustand/useToastStore"; type UniversityScreenProps = { @@ -33,7 +33,7 @@ const UniversityScreen = ({ onNext }: UniversityScreenProps) => { const verificationFile = watch("verificationFile"); // 모든 대학 목록 가져오기 - const { data: allUniversities = [], isLoading } = useGetUniversitySearchByText(""); + const { data: allUniversities = [], isLoading } = useUniversitySearch(""); // regionList에서 모든 국가 추출 (중복 제거) const availableCountries = useMemo(() => { diff --git a/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSelectUniversities.ts b/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSelectUniversities.ts index caedd26a..2e9ddfe5 100644 --- a/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSelectUniversities.ts +++ b/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSelectUniversities.ts @@ -1,7 +1,7 @@ import { useState } from "react"; -import { QueryKeys } from "@/api/university/client/queryKey"; -import useDeleteUniversityFavorite from "@/api/university/client/useDeleteUniversityFavorite"; +import { QueryKeys } from "@/apis/queryKeys"; +import { useDeleteWish } from "@/apis/universities"; import { toast } from "@/lib/zustand/useToastStore"; import { useQueryClient } from "@tanstack/react-query"; @@ -11,7 +11,7 @@ interface UseSelectUniversitiesReturn { handleDeleteAll: () => void; } const useSelectUniversities = (): UseSelectUniversitiesReturn => { - const { mutate: deleteUserAccount } = useDeleteUniversityFavorite(); + const { mutate: deleteUserAccount } = useDeleteWish(); const queryClient = useQueryClient(); const [editSelected, setEditSelected] = useState([]); @@ -43,7 +43,7 @@ const useSelectUniversities = (): UseSelectUniversitiesReturn => { } else { toast.success("모든 학교가 삭제되었습니다."); } - queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); + queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); }); }; diff --git a/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSortedUniversities.ts b/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSortedUniversities.ts index 8697e37e..30735a28 100644 --- a/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSortedUniversities.ts +++ b/src/app/my/favorite/_ui/FavoriteContent/_hooks/useSortedUniversities.ts @@ -4,7 +4,7 @@ import { filterType } from ".."; import { ListUniversity, University } from "@/types/university"; -import useGetMyWishUniversity from "@/api/university/client/useGetMyWishUniversity"; +import { useGetWishList } from "@/apis/universities"; interface UseSortedUniversitiesReturn { wishUniversity: ListUniversity[]; @@ -13,7 +13,7 @@ interface UseSortedUniversitiesReturn { } const useSortedUniversities = (): UseSortedUniversitiesReturn => { - const { data: wishUniversity = [] } = useGetMyWishUniversity(); + const { data: wishUniversity = [] } = useGetWishList(); const [sequence, setSequence] = useState(filterType.LATEST); diff --git a/src/app/university/SearchResultsContent.tsx b/src/app/university/SearchResultsContent.tsx index 644c33c6..7a0fe9df 100644 --- a/src/app/university/SearchResultsContent.tsx +++ b/src/app/university/SearchResultsContent.tsx @@ -13,10 +13,11 @@ import SearchBar from "./SearchBar"; import { CountryCode, LanguageTestType, RegionEnumExtend } from "@/types/university"; // 필요한 타입과 훅 import -import useGetUniversitySearchByFilter, { - UniversitySearchFilterParams, -} from "@/api/university/client/useGetUniversitySearchByFilter"; -import useGetUniversitySearchByText from "@/api/university/client/useGetUniversitySearchByText"; +import { + useGetUniversitySearchByFilter, + useUniversitySearch, + type UniversitySearchFilterParams, +} from "@/apis/universities"; // --- URL 파라미터를 읽고 데이터를 처리하는 메인 컨텐츠 --- const SearchResultsContent = () => { @@ -55,7 +56,7 @@ const SearchResultsContent = () => { } }, [searchParams, selectedRegion]); - const textSearchQuery = useGetUniversitySearchByText(searchText ?? ""); + const textSearchQuery = useUniversitySearch(searchText ?? ""); const filterSearchQuery = useGetUniversitySearchByFilter(filterParams); const { data: serachResult } = isTextSearch ? textSearchQuery : filterSearchQuery; diff --git a/src/app/university/[id]/_ui/UniversityDetail/_ui/UniversityBtns.tsx b/src/app/university/[id]/_ui/UniversityDetail/_ui/UniversityBtns.tsx index f1c98fd4..14adb1d5 100644 --- a/src/app/university/[id]/_ui/UniversityDetail/_ui/UniversityBtns.tsx +++ b/src/app/university/[id]/_ui/UniversityDetail/_ui/UniversityBtns.tsx @@ -2,9 +2,7 @@ import { useEffect, useState } from "react"; -import useDeleteUniversityFavorite from "@/api/university/client/useDeleteUniversityFavorite"; -import useGetMyWishUniversity from "@/api/university/client/useGetMyWishUniversity"; -import usePostUniversityFavorite from "@/api/university/client/usePostUniversityFavorite"; +import { useDeleteWish, useGetWishList, usePostAddWish } from "@/apis/universities"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; @@ -46,9 +44,9 @@ const UniversityBtns = ({ universityId }: UniversityBtnsProps) => { const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const [isLiked, setIsLiked] = useState(false); - const { data: favoriteUniv } = useGetMyWishUniversity(isAuthenticated); - const { mutate: postUniversityFavorite } = usePostUniversityFavorite(); - const { mutate: deleteUniversityFavorite } = useDeleteUniversityFavorite(); + const { data: favoriteUniv } = useGetWishList(isAuthenticated); + const { mutate: postUniversityFavorite } = usePostAddWish(); + const { mutate: deleteUniversityFavorite } = useDeleteWish(); useEffect(() => { favoriteUniv?.forEach((univ) => { diff --git a/src/app/university/[id]/page.tsx b/src/app/university/[id]/page.tsx index 30a26293..d158d5b6 100644 --- a/src/app/university/[id]/page.tsx +++ b/src/app/university/[id]/page.tsx @@ -5,8 +5,7 @@ import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import UniversityDetail from "./_ui/UniversityDetail"; -import { getAllUniversities } from "@/api/university/server/getSearchUniversitiesByText"; -import { getUniversityDetail } from "@/api/university/server/getUniversityDetail"; +import { getUniversityDetail, getAllUniversities } from "@/apis/universities/server"; export const revalidate = false; diff --git a/src/app/university/application/apply/ApplyPageContent.tsx b/src/app/university/application/apply/ApplyPageContent.tsx index 82f5f12e..c4c250fe 100644 --- a/src/app/university/application/apply/ApplyPageContent.tsx +++ b/src/app/university/application/apply/ApplyPageContent.tsx @@ -15,17 +15,16 @@ import UniversityStep from "./UniversityStep"; import { ListUniversity } from "@/types/university"; -import usePostSubmitApplication from "@/api/applications/client/usePostSubmitApplication"; -import useGetMyGpaScore from "@/api/score/client/useGetMyGpaScore"; -import useGetMyLanguageTestScore from "@/api/score/client/useGetMyLanguageTestScore"; -import useGetUniversitySearchByText from "@/api/university/client/useGetUniversitySearchByText"; +import { useGetMyGpaScore, useGetMyLanguageTestScore } from "@/apis/Scores"; +import { usePostSubmitApplication } from "@/apis/applications"; +import { useUniversitySearch } from "@/apis/universities"; import { toast } from "@/lib/zustand/useToastStore"; const ApplyPageContent = () => { const router = useRouter(); const [step, setStep] = useState(1); - const { data: universityList = [] } = useGetUniversitySearchByText(""); + const { data: universityList = [] } = useUniversitySearch(""); const { data: gpaScoreList = [] } = useGetMyGpaScore(); const { data: languageTestScoreList = [] } = useGetMyLanguageTestScore(); const { mutate: postSubmitApplication } = usePostSubmitApplication({ From 53981fca82e1462ff6711177538f3aff44c44807 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 18:55:08 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B2=A0=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=EB=90=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=B5?= =?UTF-8?q?=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UniversityDetail import 경로 수정 (../ → ./_ui/) - QueryKeys에 universityDetail 추가 - useGetRecommendedUniversity에서 useAuthStore 직접 사용 - MentorApplyCountModal 컴포넌트 복구 - ScoreCard에 toast import 추가 --- docs/api-migration-prd.md | 909 +++++------------- src/apis/Auth/api.ts | 117 ++- src/apis/Auth/deleteAccount.ts | 37 +- src/apis/Auth/index.ts | 34 +- src/apis/Auth/postAppleAuth.ts | 39 +- src/apis/Auth/postEmailLogin.ts | 37 +- src/apis/Auth/postEmailVerification.ts | 20 +- src/apis/Auth/postKakaoAuth.ts | 40 +- src/apis/Auth/postSignOut.ts | 28 +- src/apis/Auth/postSignUp.ts | 14 +- src/apis/Auth/server/index.ts | 1 + src/apis/Auth/server/postReissueToken.ts | 29 + src/apis/MyPage/api.ts | 80 +- src/apis/MyPage/getProfile.ts | 34 +- src/apis/MyPage/index.ts | 10 +- .../MyPage/patchInterestedRegionCountry.ts | 2 +- src/apis/MyPage/patchPassword.ts | 31 +- src/apis/MyPage/patchProfile.ts | 29 +- src/apis/community/api.ts | 216 ++--- src/apis/community/deleteComment.ts | 28 +- src/apis/community/deleteLikePost.ts | 26 +- src/apis/community/deletePost.ts | 32 +- src/apis/community/getPostDetail.ts | 14 +- src/apis/community/patchUpdatePost.ts | 33 +- src/apis/community/postCreateComment.ts | 27 +- src/apis/community/postCreatePost.ts | 60 +- src/apis/community/postLikePost.ts | 26 +- src/app/login/LoginContent.tsx | 2 +- .../apple/callback/AppleLoginCallbackPage.tsx | 2 +- .../kakao/callback/KakaoLoginCallbackPage.tsx | 2 +- .../chat/[chatId]/_ui/ChatNavBar/index.tsx | 2 +- .../mentor/chat/_ui/ChatPageClient/index.tsx | 2 +- src/app/my/_ui/MyProfileContent/index.tsx | 5 +- .../my/favorite/_ui/FavoriteContent/index.tsx | 2 +- src/app/my/match/_ui/MatchContent/index.tsx | 2 +- .../_hooks/useModifyUserHookform.ts | 3 +- .../my/password/_ui/PasswordContent/index.tsx | 2 +- src/app/sign-up/email/EmailSignUpForm.tsx | 2 +- .../layout/ReissueProvider/index.tsx | 2 +- src/components/login/signup/SignupSurvey.tsx | 2 +- src/utils/axiosInstance.ts | 2 +- 41 files changed, 976 insertions(+), 1009 deletions(-) create mode 100644 src/apis/Auth/server/index.ts create mode 100644 src/apis/Auth/server/postReissueToken.ts diff --git a/docs/api-migration-prd.md b/docs/api-migration-prd.md index 38260a80..98279615 100644 --- a/docs/api-migration-prd.md +++ b/docs/api-migration-prd.md @@ -1,773 +1,328 @@ -# API 마이그레이션 PRD (Product Requirements Document) +# API 마이그레이션 PRD -## api → apis 마이그레이션 프로젝트 +> `src/api` → `src/apis` 완전 마이그레이션 --- ## 1. 개요 -### 1.1 목적 +### 1.1 목표 -기존 수동 작성된 `src/api` 폴더의 코드를 자동 생성된 `src/apis` 폴더 기반으로 마이그레이션하여 API 규격을 자동화된 시스템으로 통일하고, 코드 일관성과 유지보수성을 향상시킵니다. +`src/api` 폴더를 완전히 제거하고 `src/apis` 폴더로 통합하여 API 레이어를 단일화한다. -### 1.2 배경 +### 1.2 현황 -- 현재 API 클라이언트가 수동으로 작성되어 있어 일관성 부족 -- Bruno API 스펙에서 자동 생성된 `apis` 폴더가 이미 존재 -- API 스펙 변경 시 자동 동기화가 가능한 구조로 전환 필요 -- 비즈니스 로직과 API 호출 로직의 분리 필요 - -### 1.3 범위 - -- **대상**: `src/api` 폴더의 모든 도메인 (13개 도메인) -- **영향받는 파일**: 약 58개 컴포넌트/파일 -- **예상 기간**: 25-35시간 (도메인별 순차 진행) - -### 1.4 주요 변경사항 - -- Import 경로 변경: `@/api/{domain}/client/*` → `@/apis/{domain}/*` -- QueryKey 구조 변경: 도메인별 독립 → 중앙 집중식 관리 -- API 클라이언트: 수동 작성 → 자동 생성 기반 -- 비즈니스 로직: 기존 로직 완전 보존 (리다이렉트, 토스트, 상태 관리) +| 폴더 | 상태 | 파일 수 | 설명 | +| ---------- | ------ | ------- | ---------------------------- | +| `src/api` | 레거시 | 63개 | 수동 작성, 제거 대상 | +| `src/apis` | 신규 | 진행중 | Bruno 기반 자동생성 + 커스텀 | --- -## 2. 도메인별 마이그레이션 현황 - -### 2.1 도메인 목록 및 우선순위 - -| 우선순위 | 도메인 | 훅 수 | 컴포넌트 수 | 상태 | 예상 시간 | -| -------- | ------------- | ----- | ----------- | ------ | --------- | -| 1 | universities | 10 | 8 | ✅완료 | 2-3시간 | -| 2 | Auth | 8 | 10+ | 대기 | 3-4시간 | -| 3 | community | 9 | 15+ | 대기 | 3-4시간 | -| 4 | mentor/mentee | 10 | 20+ | 대기 | 4-5시간 | -| 5 | chat | 4 | 5+ | 대기 | 2-3시간 | -| 6 | news | 7 | 8+ | 대기 | 2-3시간 | -| 7 | Scores | 4 | 5+ | 대기 | 2시간 | -| 8 | MyPage | 3 | 5+ | 대기 | 2시간 | -| 9 | applications | 3 | 3+ | 대기 | 2시간 | -| 10 | reports | 1 | 2+ | 대기 | 1시간 | -| 11 | file | 1 | 2+ | 대기 | 1시간 | -| 12 | boards | 2 | 2+ | 대기 | 1시간 | -| 13 | mentors | 2 | 3+ | 대기 | 1시간 | - -**총계**: 약 61개 훅, 90+ 컴포넌트 - -### 2.2 진행 상태 추적 - -#### universities 도메인 ✅ 완료 - -- [x] 분석 완료 -- [x] API 클라이언트 수정 완료 -- [x] 훅 마이그레이션 완료 -- [x] 컴포넌트 업데이트 완료 -- [x] Server-side API 처리 완료 -- [x] 테스트 완료 -- [x] 커밋 완료 -- [x] 정리 완료 (레거시 폴더 삭제) - -#### 기타 도메인 (대기 중) - -- [ ] 분석 완료 -- [ ] API 클라이언트 수정 완료 -- [ ] 훅 마이그레이션 완료 -- [ ] 컴포넌트 업데이트 완료 -- [ ] Server-side API 처리 완료 -- [ ] 테스트 완료 -- [ ] 커밋 완료 -- [ ] 정리 완료 - ---- +## 2. 마이그레이션 대상 -## 3. 주요 변경사항 +### 2.1 도메인별 현황 -### 3.1 Import 경로 변경 +| # | 도메인 | api 파일 | apis 존재 | 상태 | 비고 | +| --- | ------------ | -------- | --------- | ------- | ---- | +| 1 | auth | 8 | ✅ | ⏳ 대기 | | +| 2 | community | 9 | ✅ | ⏳ 대기 | | +| 3 | mentor | 7 | ✅ | ⏳ 대기 | | +| 4 | mentee | 4 | ❌ | ⏳ 대기 | | +| 5 | mentors | 3 | ❌ | ⏳ 대기 | | +| 6 | chat | 5 | ✅ | ⏳ 대기 | | +| 7 | news | 7 | ✅ | ⏳ 대기 | | +| 8 | score | 5 | ✅ | ⏳ 대기 | | +| 9 | my | 4 | ✅ | ⏳ 대기 | | +| 10 | applications | 4 | ✅ | ⏳ 대기 | | +| 11 | boards | 3 | ❌ | ⏳ 대기 | | +| 12 | file | 1 | ✅ | ⏳ 대기 | | +| 13 | reports | 1 | ✅ | ⏳ 대기 | | -**Before:** +**총계**: 63개 파일 → 0개 (완전 제거) -```typescript -import { QueryKeys } from "@/api/university/client/queryKey"; -import useGetUniversityDetail from "@/api/university/client/useGetUniversityDetail"; -``` +### 2.2 상세 파일 목록 -**After:** +#### auth (8개) -```typescript -import { QueryKeys } from "@/apis/queryKeys"; -import useGetUniversityDetail from "@/apis/universities/get-getUniversityDetail"; ``` - -**통계:** - -- 변경 예상 파일 수: 58개 -- Import 문 변경 수: 약 100+ 개 - -### 3.2 QueryKey 구조 변경 - -**Before (도메인별 독립):** - -```typescript -// api/university/client/queryKey.ts -export enum QueryKeys { - universityDetail = "universityDetail", - recommendedUniversity = "recommendedUniversity", -} +src/api/auth/ +├── server/ +│ └── postReissueToken.ts # 서버사이드 유지 필요 +├── client/ +│ ├── usePostKakaoAuth.ts +│ ├── usePostAppleAuth.ts +│ ├── usePostEmailAuth.ts +│ ├── usePostSignUp.ts +│ ├── usePostEmailSignUp.ts +│ ├── usePostLogout.ts +│ └── useDeleteUserAccount.ts +└── useLogin.ts ``` -**After (중앙 집중식):** +#### community (9개) -```typescript -// apis/queryKeys.ts -export const QueryKeys = { - universities: { - universityDetail: "universities.universityDetail", - recommendedUniversities: "universities.recommendedUniversities", - }, -}; ``` - -**변경 영향:** - -- QueryKey 사용처: 약 50+ 곳 -- `queryClient.invalidateQueries()` 호출: 약 30+ 곳 - -### 3.3 비즈니스 로직 보존 현황 - -다음 비즈니스 로직은 100% 보존됩니다: - -| 로직 유형 | 파일 수 | 보존 방법 | -| ------------------- | ------- | -------------------------------------- | -| 리다이렉트 로직 | 7 | `onSuccess`/`onError` 콜백에 유지 | -| 토스트 메시지 | 25 | 모든 토스트 호출 유지 | -| 상태 관리 (Zustand) | 10+ | `useAuthStore` 호출 유지 | -| 쿼리 무효화 | 30+ | `queryClient.invalidateQueries()` 유지 | -| 커스텀 다이얼로그 | 5+ | `customAlert`/`customConfirm` 유지 | - -### 3.4 타입 호환성 처리 - -**전략:** - -- 가능한 경우 `select` 옵션 사용하여 타입 변환 -- 필요한 경우 타입 어설션 최소화 사용 -- 기존 타입과 새 타입 매핑 테이블 작성 - -**예상 타입 불일치:** - -- 약 10-15개 훅에서 타입 매핑 필요 예상 - ---- - -## 4. 기술적 세부사항 - -### 4.1 API 클라이언트 수정 내역 - -**현재 문제:** - -- `apis/{domain}/api.ts`의 일부 URL이 `{`로 되어 있음 (자동 생성 미완료) - -**수정 방법:** - -- 기존 `api` 폴더의 엔드포인트 확인 -- `apis/api.ts`의 URL을 실제 엔드포인트로 수정 -- `publicAxiosInstance` vs `axiosInstance` 구분 확인 및 적용 - -**예시:** - -```typescript -// Before (apis/universities/api.ts) -getUniversityDetail: async (params) => { - const res = await axiosInstance.get(`{`, { params }); - return res.data; -}; - -// After -getUniversityDetail: async (universityInfoForApplyId: number) => { - const res = await publicAxiosInstance.get(`/univ-apply-infos/${universityInfoForApplyId}`); - return res.data; -}; +src/api/community/client/ +├── queryKey.ts +├── useGetPostDetail.ts +├── useCreatePost.ts +├── useUpdatePost.ts +├── useDeletePost.ts +├── useCreateComment.ts +├── useDeleteComment.ts +├── usePostLike.ts +└── useDeleteLike.ts ``` -### 4.2 비즈니스 로직 마이그레이션 패턴 - -#### 패턴 1: 리다이렉트 로직 - -```typescript -// 기존 -const usePostKakaoAuth = () => { - return useMutation({ - mutationFn: postKakaoAuth, - onSuccess: (response) => { - if (data.isRegistered) { - router.replace(safeRedirect); - } else { - router.push(`/sign-up?token=${data.signUpToken}`); - } - }, - }); -}; +#### mentor (7개) -// 새로운 (apis 래핑) -import { authApi } from "./api"; -const usePostKakaoAuth = () => { - return useMutation({ - mutationFn: (data) => authApi.postKakaoAuth({ data }), - onSuccess: (response) => { - // 기존 비즈니스 로직 100% 유지 - if (response.isRegistered) { - router.replace(safeRedirect); - } else { - router.push(`/sign-up?token=${response.signUpToken}`); - } - }, - }); -}; ``` - -#### 패턴 2: 토스트 + 쿼리 무효화 - -```typescript -// 새로운 -import { QueryKeys } from "../queryKeys"; -import { universitiesApi } from "./api"; - -// 기존 -const usePostUniversityFavorite = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: postUniversityFavoriteApi, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); - }, - onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), - }); -}; - -const usePostAddWish = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (id) => universitiesApi.postAddWish(id), - onSuccess: () => { - // QueryKey 구조 변경 반영 - queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); - }, - onError: createMutationErrorHandler("위시리스트 추가에 실패했습니다."), - }); -}; +src/api/mentor/client/ +├── queryKey.ts +├── useGetMentorMyProfile.ts +├── usePutMyMentorProfile.ts +├── usePostMentorApplication.ts +├── useGetMentoringList.ts +├── useGetMentoringUncheckedCount.ts +├── usePatchMentorCheckMentorings.ts +└── usePatchApprovalStatus.ts ``` -### 4.3 타입 매핑 전략 +#### mentee (4개) -**전략 1: select 옵션 사용 (권장)** - -```typescript -useQuery({ - queryFn: () => universitiesApi.getUniversityDetail(id), - select: (data) => data as unknown as University, // 타입 변환 -}); ``` - -**전략 2: 타입 어설션 (최소화)** - -```typescript -const data = await universitiesApi.getWishList(); -return data as unknown as ListUniversity[]; +src/api/mentee/client/ +├── queryKey.ts +├── useGetApplyMentoringList.ts +├── usePostApplyMentoring.ts +└── usePatchMenteeCheckMentorings.ts ``` -### 4.4 Server-side API 처리 방법 - -**현재 구조:** - -- `api/{domain}/server/*.ts`에서 `serverFetchUtil` 사용 -- ISR/SSR을 위한 공개 API 호출 - -**마이그레이션 방법:** - -```typescript -// 기존 -import serverFetch from "@/utils/serverFetchUtil"; -const getRecommendedUniversity = async () => { - const res = await serverFetch(endpoint); - return res; -}; +#### mentors (3개) -// 새로운 (apis API를 serverFetchUtil로 래핑) -import { universitiesApi } from "@/apis/universities/api"; -import serverFetch from "@/utils/serverFetchUtil"; - -// 옵션 1: apis API 직접 사용 (클라이언트 전용) -// 옵션 2: serverFetchUtil 래퍼 생성 -const getRecommendedUniversity = async () => { - // serverFetchUtil 사용 유지 (ISR/SSR 호환) - const endpoint = "/univ-apply-infos/recommend"; - const res = await serverFetch(endpoint); - return res; -}; ``` - -**권장 접근:** - -- Server-side는 기존 `serverFetchUtil` 유지 -- Client-side만 `apis` 사용 -- 필요시 `apis` API를 `serverFetchUtil`로 래핑 - ---- - -## 5. 영향도 분석 - -### 5.1 변경된 파일 수 - -| 카테고리 | 파일 수 | -| ---------------- | -------------------- | -| API 훅 파일 | 61개 (마이그레이션) | -| 컴포넌트 파일 | 90+ 개 (import 변경) | -| QueryKey 파일 | 13개 (제거) | -| Server-side 파일 | 5개 (유지 또는 래핑) | -| **총계** | **170+ 개** | - -### 5.2 영향받는 주요 컴포넌트 - -**높은 영향도:** - -- `src/app/university/**` - 15+ 파일 -- `src/app/mentor/**` - 20+ 파일 -- `src/app/community/**` - 15+ 파일 -- `src/app/my/**` - 10+ 파일 -- `src/app/login/**` - 5+ 파일 - -**중간 영향도:** - -- `src/components/mentor/**` - 10+ 파일 -- `src/components/**` - 15+ 파일 - -### 5.3 잠재적 위험 요소 - -| 위험 요소 | 심각도 | 완화 전략 | -| --------------------------------------- | ------ | -------------------------------------- | -| 타입 불일치로 인한 런타임 오류 | 높음 | 철저한 타입 검증, `select` 옵션 활용 | -| 비즈니스 로직 누락 (리다이렉트, 토스트) | 높음 | 체크리스트 작성, 코드 리뷰 | -| QueryKey 변경으로 인한 캐시 문제 | 중간 | 모든 `invalidateQueries` 호출 검증 | -| Server-side 호환성 문제 | 중간 | ISR/SSR 테스트, `serverFetchUtil` 유지 | -| Import 경로 오류 | 낮음 | 자동화된 검색/교체, Linter 활용 | - -### 5.4 테스트 커버리지 - -**필수 테스트 항목:** - -- [ ] 각 도메인별 기능 테스트 -- [ ] 인증 플로우 테스트 (Auth 도메인) -- [ ] 리다이렉트 동작 확인 -- [ ] 토스트 메시지 표시 확인 -- [ ] 쿼리 캐시 무효화 확인 -- [ ] 타입 오류 확인 (TypeScript) -- [ ] 빌드 테스트 -- [ ] Linter 오류 확인 - ---- - -## 6. 마이그레이션 체크리스트 - -### 6.1 도메인별 완료 상태 - -각 도메인마다 다음 체크리스트를 완료해야 합니다: - -#### [도메인명] 마이그레이션 체크리스트 - -**1단계: 분석 및 준비** - -- [ ] 기존 도메인 폴더 구조 파악 -- [ ] `apis` 폴더의 해당 도메인 구조 확인 -- [ ] 비즈니스 로직 목록 작성 -- [ ] 사용처 파악 (import 찾기) -- [ ] QueryKey 매핑 테이블 작성 - -**2단계: API 클라이언트 수정** - -- [ ] `apis/{domain}/api.ts`의 URL 수정 -- [ ] 타입 정의 확인 및 매핑 -- [ ] `publicAxiosInstance` vs `axiosInstance` 구분 - -**3단계: 훅 마이그레이션** - -- [ ] 기존 훅의 비즈니스 로직 분석 -- [ ] `apis`의 기본 훅 확인 -- [ ] 비즈니스 로직을 포함한 래퍼 훅 생성 -- [ ] QueryKey 업데이트 -- [ ] 타입 호환성 확인 - -**4단계: 컴포넌트 업데이트** - -- [ ] 해당 도메인을 사용하는 모든 컴포넌트 찾기 -- [ ] Import 경로 변경 -- [ ] 타입 import 업데이트 -- [ ] QueryKey 사용처 업데이트 - -**5단계: Server-side API 처리** - -- [ ] `api/{domain}/server/*.ts` 파일 확인 -- [ ] `serverFetchUtil` 사용 여부 확인 -- [ ] ISR/SSR 호환성 유지 -- [ ] 필요시 래퍼 함수 생성 - -**6단계: 테스트 및 검증** - -- [ ] 기능 테스트 -- [ ] 타입 오류 확인 -- [ ] Linter 오류 확인 -- [ ] 빌드 테스트 - -**7단계: 커밋** - -- [ ] 모든 변경사항 스테이징 -- [ ] 커밋 메시지 작성 -- [ ] 커밋 완료 - -**8단계: 정리** - -- [ ] 기존 `api/{domain}` 폴더 제거 -- [ ] 사용하지 않는 import 정리 -- [ ] 문서 업데이트 (필요시) - -### 6.2 남은 작업 항목 - -**전체 진행률**: 0% (시작 전) - -**다음 작업:** - -1. universities 도메인 마이그레이션 시작 -2. 각 단계별 검증 -3. 진행 상황 기록 - -### 6.3 예상 완료 시점 - -- **시작일**: [작업 시작일] -- **예상 완료일**: [시작일 + 25-35시간] -- **도메인당 평균**: 2-4시간 - ---- - -## 7. 부록 - -### 7.1 도메인별 상세 변경 내역 - -각 도메인 마이그레이션 완료 후 다음 정보를 기록합니다: - -#### [도메인명] 상세 변경 내역 - -**마이그레이션된 훅:** - -- `useXXX` → `apis/{domain}/xxx-xxx` - -**변경된 컴포넌트:** - -- `src/app/...` (N개 파일) -- `src/components/...` (N개 파일) - -**QueryKey 변경:** - -- `QueryKeys.oldKey` → `QueryKeys.domain.newKey` - -**비즈니스 로직:** - -- 리다이렉트: N개 유지 -- 토스트: N개 유지 -- 상태 관리: N개 유지 - -**제거된 파일:** - -- `api/{domain}/client/*.ts` (N개) -- `api/{domain}/server/*.ts` (N개, 또는 유지) - -### 7.2 코드 예시 (Before/After) - -#### 예시 1: 단순 Query 훅 - -**Before:** - -```typescript -// api/university/client/useGetUniversityDetail.ts -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useQuery } from "@tanstack/react-query"; - -const getUniversityDetail = async (id: number) => { - const response = await publicAxiosInstance.get(`/univ-apply-infos/${id}`); - return response.data; -}; - -const useGetUniversityDetail = (id: number) => { - return useQuery({ - queryKey: [QueryKeys.universityDetail, id], - queryFn: () => getUniversityDetail(id), - enabled: !!id, - }); -}; +src/api/mentors/client/ +├── queryKey.ts +├── useGetMentorList.ts +└── useGetMentorDetail.ts ``` -**After:** +#### chat (5개) -```typescript -// apis/universities/get-getUniversityDetail.ts -import { QueryKeys } from "../queryKeys"; -import { UniversityDetailResponse, universitiesApi } from "./api"; - -import { University } from "@/types/university"; +``` +src/api/chat/clients/ +├── queryKey.ts +├── useGetChatRooms.ts +├── useGetChatHistories.ts +├── useGetPartnerInfo.ts +└── usePutChatRead.ts +``` -import { useQuery } from "@tanstack/react-query"; +#### news (7개) -const useGetUniversityDetail = (id: number) => { - return useQuery({ - queryKey: [QueryKeys.universities.universityDetail, id], - queryFn: () => universitiesApi.getUniversityDetail(id), - enabled: !!id, - select: (data) => data as unknown as University, - }); -}; +``` +src/api/news/client/ +├── queryKey.ts +├── useGetArticleList.ts +├── usePostAddArticle.ts +├── usePutModifyArticle.ts +├── useDeleteArticle.ts +├── usePostArticleLike.ts +└── useDeleteArticleLike.ts ``` -#### 예시 2: 비즈니스 로직 포함 Mutation 훅 - -**Before:** +#### score (5개) -```typescript -// api/auth/client/usePostKakaoAuth.ts -const usePostKakaoAuth = () => { - const { setAccessToken } = useAuthStore(); - const router = useRouter(); - const searchParams = useSearchParams(); - - return useMutation({ - mutationFn: postKakaoAuth, - onSuccess: (response) => { - const { data } = response; - if (data.isRegistered) { - setAccessToken(data.accessToken); - const redirectParam = searchParams.get("redirect"); - const safeRedirect = validateSafeRedirect(redirectParam); - toast.success("로그인에 성공했습니다."); - router.replace(safeRedirect); - } else { - router.push(`/sign-up?token=${data.signUpToken}`); - } - }, - onError: () => { - toast.error("카카오 로그인 중 오류가 발생했습니다."); - router.push("/login"); - }, - }); -}; +``` +src/api/score/client/ +├── queryKey.ts +├── useGetMyGpaScore.ts +├── usePostGpaScore.ts +├── useGetMyLanguageTestScore.ts +└── usePostLanguageTestScore.ts ``` -**After:** +#### my (4개) -```typescript -// apis/Auth/post-postKakaoAuth.ts (래퍼 훅) -import { useRouter, useSearchParams } from "next/navigation"; - -import { validateSafeRedirect } from "@/utils/authUtils"; - -import { KakaoAuthResponse, authApi } from "./api"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -const usePostKakaoAuth = () => { - const { setAccessToken } = useAuthStore(); - const router = useRouter(); - const searchParams = useSearchParams(); - - return useMutation({ - mutationFn: (data) => authApi.postKakaoAuth({ data }), - onSuccess: (response) => { - // 기존 비즈니스 로직 100% 유지 - if (response.isRegistered) { - setAccessToken(response.accessToken); - const redirectParam = searchParams.get("redirect"); - const safeRedirect = validateSafeRedirect(redirectParam); - toast.success("로그인에 성공했습니다."); - router.replace(safeRedirect); - } else { - router.push(`/sign-up?token=${response.signUpToken}`); - } - }, - onError: () => { - toast.error("카카오 로그인 중 오류가 발생했습니다."); - router.push("/login"); - }, - }); -}; +``` +src/api/my/client/ +├── queryKey.ts +├── useGetMyInfo.ts +├── usePatchMyInfo.ts +└── usePatchMyPassword.ts ``` -### 7.3 마이그레이션 가이드 +#### applications (4개) -#### 단계별 가이드 +``` +src/api/applications/client/ +├── queryKeys.ts +├── useGetApplicationsList.ts +├── usePostSubmitApplication.ts +└── useGetCompetitorsApplicationList.ts +``` -**1. 도메인 선택 및 분석** +#### boards (3개) -```bash -# 1. 도메인 폴더 확인 -ls src/api/{domain}/client/ +``` +src/api/boards/ +├── clients/ +│ ├── QueryKeys.ts +│ └── useGetPostList.ts +└── server/ + └── getPostList.ts +``` -# 2. 사용처 찾기 -grep -r "from.*@/api/{domain}" src/ +#### file (1개) -# 3. apis 폴더 확인 -ls src/apis/{domain}/ +``` +src/api/file/client/ +└── useUploadProfileImagePublic.ts ``` -**2. API 클라이언트 수정** - -- `apis/{domain}/api.ts` 파일 열기 -- URL이 `{`로 되어 있으면 기존 `api` 폴더의 엔드포인트 확인 -- `publicAxiosInstance` vs `axiosInstance` 구분 확인 +#### reports (1개) -**3. 훅 마이그레이션** +``` +src/api/reports/client/ +└── usePostReport.ts +``` -- 기존 훅 파일 열기 -- 비즈니스 로직 파악 (리다이렉트, 토스트, 상태 관리) -- `apis`의 기본 훅 확인 -- 비즈니스 로직을 포함한 래퍼 훅 작성 +--- -**4. 컴포넌트 업데이트** +## 3. 마이그레이션 규칙 -```bash -# Import 경로 일괄 변경 (주의: 수동 검증 필요) -find src/ -type f -name "*.tsx" -o -name "*.ts" | xargs sed -i '' \ - 's|@/api/{domain}/client/|@/apis/{domain}/|g' -``` +### 3.1 네이밍 컨벤션 -**5. QueryKey 업데이트** +| 항목 | Before (api) | After (apis) | +| -------- | ------------------------ | ------------------------- | +| Query 훅 | `useGetXxx.ts` | `useGetXxx.ts` (동일) | +| Mutation | `usePostXxx.ts` | `usePostXxx.ts` (동일) | +| QueryKey | `queryKey.ts` (도메인별) | `queryKeys.ts` (중앙집중) | +| API 함수 | 훅 내부 정의 | `api.ts` 에서 export | +| Import | `@/api/{domain}/client/` | `@/apis/{domain}/` | -- `apis/queryKeys.ts`에서 해당 도메인 키 확인 -- 모든 사용처에서 `QueryKeys.old` → `QueryKeys.domain.new` 변경 +### 3.2 QueryKey 통합 -**6. 테스트** +**Before** (각 도메인별 분산): -```bash -# 타입 체크 -npm run type-check +```typescript +// src/api/community/client/queryKey.ts +export enum QueryKeys { + postDetail = "postDetail", + postList = "postList", +} +``` -# Linter -npm run lint +**After** (중앙 집중): -# 빌드 -npm run build +```typescript +// src/apis/queryKeys.ts +export const QueryKeys = { + community: { + postDetail: "community.postDetail", + postList: "community.postList", + }, + // ... +}; ``` -**7. 커밋** +### 3.3 비즈니스 로직 보존 -```bash -git add . -git commit -m "refactor: migrate {domain} from api to apis +마이그레이션 시 다음 로직은 **반드시** 보존: -Changes: -- Migrate {N} hooks from api/{domain} to apis/{domain} -- Preserve business logic (redirects, toasts, state management) -- Update QueryKeys: {old} → {new} -- Update {N} component imports -- Remove api/{domain} folder" -``` +- [ ] `router.push()` / `router.replace()` 리다이렉트 +- [ ] `toast.success()` / `toast.error()` 알림 +- [ ] `useAuthStore` 상태 관리 +- [ ] `queryClient.invalidateQueries()` 캐시 무효화 +- [ ] `onSuccess` / `onError` 콜백 로직 -### 7.4 커밋 메시지 템플릿 +--- -``` -refactor: migrate {domain} from api to apis +## 4. 작업 체크리스트 -Changes: -- Migrate {N} hooks from api/{domain} to apis/{domain} -- Preserve business logic (redirects, toasts, state management) -- Update QueryKeys: {old structure} → {new structure} -- Update {N} component imports -- Remove api/{domain} folder +### 4.1 도메인별 체크리스트 템플릿 -Files changed: -- src/apis/{domain}/* (modified/created) -- src/app/**/* (imports updated) -- src/components/**/* (imports updated) -- src/api/{domain}/* (removed) +```markdown +#### [도메인명] 마이그레이션 -Breaking changes: None (internal refactoring) +- [ ] api.ts URL/메서드 확인 및 수정 +- [ ] 훅 마이그레이션 (비즈니스 로직 보존) +- [ ] QueryKey 통합 +- [ ] 컴포넌트 import 경로 변경 +- [ ] 서버사이드 API 처리 (해당시) +- [ ] TypeScript 에러 확인 +- [ ] 기능 테스트 +- [ ] 레거시 파일 삭제 ``` -### 7.5 비즈니스 로직 체크리스트 - -각 훅 마이그레이션 시 다음 항목을 확인: - -- [ ] 리다이렉트 로직 유지 (`router.push`, `router.replace`) -- [ ] 토스트 메시지 유지 (`toast.success`, `toast.error`) -- [ ] 상태 관리 유지 (`useAuthStore`, `setAccessToken`, `clearAccessToken`) -- [ ] 쿼리 무효화 유지 (`queryClient.invalidateQueries`) -- [ ] 커스텀 다이얼로그 유지 (`customAlert`, `customConfirm`) -- [ ] 에러 핸들링 유지 (`onError` 콜백) -- [ ] 조건부 로직 유지 (`if/else` 분기) +### 4.2 전체 진행 상황 + +| 도메인 | 분석 | 마이그레이션 | 테스트 | 삭제 | 완료 | +| ------------ | ---- | ------------ | ------ | ---- | ---- | +| auth | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| community | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| mentor | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| mentee | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| mentors | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| chat | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| news | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| score | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| my | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| applications | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| boards | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| file | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| reports | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | + +**범례**: ⬜ 대기 | 🔄 진행중 | ✅ 완료 --- -## 8. 리스크 관리 - -### 8.1 주요 리스크 및 완화 전략 +## 5. 우선순위 -| 리스크 | 심각도 | 확률 | 완화 전략 | -| -------------------------------- | ------ | ---- | ----------------------------------------------------------- | -| 타입 불일치로 인한 런타임 오류 | 높음 | 중간 | 철저한 타입 검증, `select` 옵션 활용, 타입 매핑 테이블 작성 | -| 비즈니스 로직 누락 | 높음 | 낮음 | 체크리스트 작성, 코드 리뷰, 단계별 검증 | -| QueryKey 변경으로 인한 캐시 문제 | 중간 | 낮음 | 모든 `invalidateQueries` 호출 검증, 테스트 | -| Server-side 호환성 문제 | 중간 | 낮음 | ISR/SSR 테스트, `serverFetchUtil` 유지 | -| Import 경로 오류 | 낮음 | 낮음 | 자동화된 검색/교체, Linter 활용 | +### 5.1 권장 순서 -### 8.2 롤백 계획 +1. **auth** - 인증 로직, 가장 중요 +2. **my** - 내 정보, auth와 연관 +3. **community** - 커뮤니티 기능 +4. **mentor/mentee/mentors** - 멘토링 기능 (함께 진행) +5. **chat** - 채팅 기능 +6. **news** - 뉴스/아티클 +7. **score** - 성적 관리 +8. **applications** - 지원 관리 +9. **boards** - 게시판 +10. **file** - 파일 업로드 +11. **reports** - 신고 기능 -각 도메인 마이그레이션 후 문제 발생 시: +### 5.2 의존성 주의사항 -1. 해당 도메인의 커밋만 롤백 -2. 기존 `api/{domain}` 폴더 복원 -3. Import 경로 복원 -4. 문제 분석 후 재시도 +- `auth/server/postReissueToken.ts` → axios interceptor에서 사용 +- `mentor/mentee` → QueryKey 공유 가능성 확인 +- `boards/community` → 유사 기능, 통합 검토 --- -## 9. 성공 기준 +## 6. 완료 조건 -### 9.1 기능적 요구사항 - -- [ ] 모든 도메인 마이그레이션 완료 -- [ ] 기존 기능 100% 동작 보장 -- [ ] 비즈니스 로직 100% 보존 -- [ ] 타입 오류 0개 -- [ ] Linter 오류 0개 +- [ ] `src/api` 폴더 완전 삭제 +- [ ] 모든 import가 `@/apis/` 경로 사용 +- [ ] TypeScript 에러 0개 +- [ ] ESLint 에러 0개 - [ ] 빌드 성공 - -### 9.2 비기능적 요구사항 - -- [ ] 코드 일관성 향상 -- [ ] 유지보수성 향상 -- [ ] API 스펙 자동 동기화 가능 -- [ ] 성능 저하 없음 +- [ ] 모든 기능 정상 동작 --- -## 10. 참고 자료 +## 7. 커밋 컨벤션 -### 10.1 관련 문서 - -- [마이그레이션 계획서](./api_마이그레이션_통합_계획.md) -- [온보딩 문서](./onboarding.md) -- [Git/CICD 가이드](./git-cicd.md) - -### 10.2 관련 파일 +``` +refactor: migrate {domain} from api to apis -- `src/api/` - 기존 API 폴더 -- `src/apis/` - 새로운 API 폴더 (자동 생성) -- `src/apis/queryKeys.ts` - 중앙 집중식 QueryKey -- `src/utils/axiosInstance.ts` - Axios 인스턴스 -- `src/utils/serverFetchUtil.ts` - Server-side fetch 유틸 +- Migrate {N} hooks to apis/{domain} +- Update component imports +- Remove legacy api/{domain} folder +``` --- -**문서 버전**: 1.0 -**작성일**: [작성일] -**최종 수정일**: [수정일] -**작성자**: Development Team +**최종 수정일**: 2025-12-28 diff --git a/src/apis/Auth/api.ts b/src/apis/Auth/api.ts index 7d6ac3be..21023614 100644 --- a/src/apis/Auth/api.ts +++ b/src/apis/Auth/api.ts @@ -1,18 +1,29 @@ -import { axiosInstance } from "@/utils/axiosInstance"; +import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; export type SignOutResponse = Record; export type SignOutRequest = Record; -export interface AppleAuthResponse { - isRegistered: boolean; +// Apple Auth Types +export interface RegisteredAppleAuthResponse { + isRegistered: true; + accessToken: string; + refreshToken: string; +} + +export interface UnregisteredAppleAuthResponse { + isRegistered: false; nickname: null; email: string; profileImageUrl: null; signUpToken: string; } -export type AppleAuthRequest = Record; +export type AppleAuthResponse = RegisteredAppleAuthResponse | UnregisteredAppleAuthResponse; + +export interface AppleAuthRequest { + code: string; +} export interface RefreshTokenResponse { accessToken: string; @@ -25,23 +36,40 @@ export interface EmailLoginResponse { refreshToken: string; } -export type EmailLoginRequest = Record; +export interface EmailLoginRequest { + email: string; + password: string; +} export interface EmailVerificationResponse { signUpToken: string; } -export type EmailVerificationRequest = Record; +export interface EmailVerificationRequest { + email: string; + verificationCode: string; +} + +// Kakao Auth Types +export interface RegisteredKakaoAuthResponse { + isRegistered: true; + accessToken: string; + refreshToken: string; +} -export interface KakaoAuthResponse { - isRegistered: boolean; +export interface UnregisteredKakaoAuthResponse { + isRegistered: false; nickname: string; email: string; profileImageUrl: string; signUpToken: string; } -export type KakaoAuthRequest = Record; +export type KakaoAuthResponse = RegisteredKakaoAuthResponse | UnregisteredKakaoAuthResponse; + +export interface KakaoAuthRequest { + code: string; +} export type AccountResponse = void; @@ -50,63 +78,68 @@ export interface SignUpResponse { refreshToken: string; } -export type SignUpRequest = Record; +export interface SignUpRequest { + signUpToken: string; + nickname: string; + profileImageUrl: string; + preparationStage: string; + interestedRegions: string[]; + interestedCountries: string[]; +} + +export interface EmailSignUpRequest { + email: string; + password: string; +} + +export interface EmailSignUpResponse { + signUpToken: string; +} export const authApi = { - postSignOut: async (params: { data?: SignOutRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/sign-out`, params?.data - ); + postSignOut: async (): Promise => { + const res = await axiosInstance.post(`/auth/sign-out`); return res.data; }, - postAppleAuth: async (params: { data?: AppleAuthRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/apple`, params?.data - ); + postAppleAuth: async (data: AppleAuthRequest): Promise => { + const res = await publicAxiosInstance.post(`/auth/apple`, data); return res.data; }, - postRefreshToken: async (params: { data?: RefreshTokenRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/reissue`, params?.data - ); + postRefreshToken: async (): Promise => { + const res = await publicAxiosInstance.post(`/auth/reissue`); return res.data; }, - postEmailLogin: async (params: { data?: EmailLoginRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/email/sign-in`, params?.data - ); + postEmailLogin: async (data: EmailLoginRequest): Promise => { + const res = await publicAxiosInstance.post(`/auth/email/sign-in`, data); return res.data; }, - postEmailVerification: async (params: { data?: EmailVerificationRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/email/sign-up`, params?.data - ); + postEmailSignUp: async (data: EmailSignUpRequest): Promise => { + const res = await publicAxiosInstance.post(`/auth/email/sign-up`, data); return res.data; }, - postKakaoAuth: async (params: { data?: KakaoAuthRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/kakao`, params?.data - ); + postKakaoAuth: async (data: KakaoAuthRequest): Promise => { + const res = await publicAxiosInstance.post(`/auth/kakao`, data); return res.data; }, deleteAccount: async (): Promise => { - const res = await axiosInstance.delete( - `/auth/quit` - ); + const res = await axiosInstance.delete(`/auth/quit`); return res.data; }, - postSignUp: async (params: { data?: SignUpRequest }): Promise => { - const res = await axiosInstance.post( - `/auth/sign-up`, params?.data - ); + postSignUp: async (data: SignUpRequest): Promise => { + // 임시 성별, 생년월일 추가. API 변경 시 삭제 + const payload = { + ...data, + birth: "2000-01-01", + gender: "PREFER_NOT_TO_SAY", + }; + const res = await publicAxiosInstance.post(`/auth/sign-up`, payload); return res.data; }, - }; \ No newline at end of file diff --git a/src/apis/Auth/deleteAccount.ts b/src/apis/Auth/deleteAccount.ts index 00038562..2c0c24ba 100644 --- a/src/apis/Auth/deleteAccount.ts +++ b/src/apis/Auth/deleteAccount.ts @@ -1,11 +1,36 @@ +import { useRouter } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { authApi, AccountResponse, AccountRequest } from "./api"; -const useDeleteAccount = () => { - return useMutation({ - mutationFn: (data) => authApi.deleteAccount({ data }), +import { authApi, AccountResponse } from "./api"; + +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +/** + * @description 회원탈퇴를 위한 useMutation 커스텀 훅 + */ +const useDeleteUserAccount = () => { + const router = useRouter(); + const { clearAccessToken } = useAuthStore(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => authApi.deleteAccount(), + onMutate: () => { + // 낙관적 업데이트: 요청이 시작되면 바로 홈으로 이동 + router.replace("/"); + }, + onSuccess: () => { + // Zustand persist가 자동으로 localStorage에서 제거 + clearAccessToken(); + queryClient.clear(); + }, + onError: () => { + toast.error("회원탈퇴에 실패했습니다. 잠시 후 다시 시도해주세요."); + }, }); }; -export default useDeleteAccount; \ No newline at end of file +export default useDeleteUserAccount; \ No newline at end of file diff --git a/src/apis/Auth/index.ts b/src/apis/Auth/index.ts index 844947a4..da505d67 100644 --- a/src/apis/Auth/index.ts +++ b/src/apis/Auth/index.ts @@ -1,9 +1,25 @@ -export { authApi } from './api'; -export { default as deleteAccount } from './deleteAccount'; -export { default as postAppleAuth } from './postAppleAuth'; -export { default as postEmailLogin } from './postEmailLogin'; -export { default as postEmailVerification } from './postEmailVerification'; -export { default as postKakaoAuth } from './postKakaoAuth'; -export { default as postRefreshToken } from './postRefreshToken'; -export { default as postSignOut } from './postSignOut'; -export { default as postSignUp } from './postSignUp'; +export { authApi } from "./api"; +export type { + KakaoAuthRequest, + KakaoAuthResponse, + AppleAuthRequest, + AppleAuthResponse, + EmailLoginRequest, + EmailLoginResponse, + SignUpRequest, + SignUpResponse, + EmailSignUpRequest, + EmailSignUpResponse, +} from "./api"; + +// Client-side hooks +export { default as useDeleteUserAccount } from "./deleteAccount"; +export { default as usePostAppleAuth } from "./postAppleAuth"; +export { default as usePostEmailAuth } from "./postEmailLogin"; +export { default as usePostEmailSignUp } from "./postEmailVerification"; +export { default as usePostKakaoAuth } from "./postKakaoAuth"; +export { default as usePostLogout } from "./postSignOut"; +export { default as usePostSignUp } from "./postSignUp"; + +// Server-side functions +export { postReissueToken } from "./server"; diff --git a/src/apis/Auth/postAppleAuth.ts b/src/apis/Auth/postAppleAuth.ts index cb30b87b..6289fd1b 100644 --- a/src/apis/Auth/postAppleAuth.ts +++ b/src/apis/Auth/postAppleAuth.ts @@ -1,10 +1,45 @@ +import { useRouter, useSearchParams } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; + +import { validateSafeRedirect } from "@/utils/authUtils"; + import { authApi, AppleAuthResponse, AppleAuthRequest } from "./api"; +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; + +/** + * @description 애플 로그인을 위한 useMutation 커스텀 훅 + */ const usePostAppleAuth = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + return useMutation({ - mutationFn: (data) => authApi.postAppleAuth({ data }), + mutationFn: (data) => authApi.postAppleAuth(data), + onSuccess: (data) => { + if (data.isRegistered) { + // 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장 + // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 + useAuthStore.getState().setAccessToken(data.accessToken); + + // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 + const redirectParam = searchParams.get("redirect"); + const safeRedirect = validateSafeRedirect(redirectParam); + + toast.success("로그인에 성공했습니다."); + router.replace(safeRedirect); + } else { + // 새로운 회원일 시 - 회원가입 페이지로 이동 + router.push(`/sign-up?token=${data.signUpToken}`); + } + }, + onError: () => { + toast.error("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요."); + router.push("/login"); + }, }); }; diff --git a/src/apis/Auth/postEmailLogin.ts b/src/apis/Auth/postEmailLogin.ts index d8ef06df..90c0bce0 100644 --- a/src/apis/Auth/postEmailLogin.ts +++ b/src/apis/Auth/postEmailLogin.ts @@ -1,11 +1,40 @@ +import { useRouter, useSearchParams } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; + +import { validateSafeRedirect } from "@/utils/authUtils"; + import { authApi, EmailLoginResponse, EmailLoginRequest } from "./api"; -const usePostEmailLogin = () => { +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; + +/** + * @description 이메일 로그인을 위한 useMutation 커스텀 훅 + */ +const usePostEmailAuth = () => { + const { setAccessToken } = useAuthStore(); + const searchParams = useSearchParams(); + const router = useRouter(); + return useMutation({ - mutationFn: (data) => authApi.postEmailLogin({ data }), + mutationFn: (data) => authApi.postEmailLogin(data), + onSuccess: (data) => { + const { accessToken } = data; + + // Zustand persist가 자동으로 localStorage에 저장 + // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 + setAccessToken(accessToken); + + // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 + const redirectParam = searchParams.get("redirect"); + const safeRedirect = validateSafeRedirect(redirectParam); + + toast.success("로그인에 성공했습니다."); + router.replace(safeRedirect); + }, }); }; -export default usePostEmailLogin; \ No newline at end of file +export default usePostEmailAuth; \ No newline at end of file diff --git a/src/apis/Auth/postEmailVerification.ts b/src/apis/Auth/postEmailVerification.ts index 99aefafe..059f5d69 100644 --- a/src/apis/Auth/postEmailVerification.ts +++ b/src/apis/Auth/postEmailVerification.ts @@ -1,11 +1,21 @@ import { AxiosError } from "axios"; + +import { authApi, EmailSignUpResponse, EmailSignUpRequest } from "./api"; + +import { toast } from "@/lib/zustand/useToastStore"; import { useMutation } from "@tanstack/react-query"; -import { authApi, EmailVerificationResponse, EmailVerificationRequest } from "./api"; -const usePostEmailVerification = () => { - return useMutation({ - mutationFn: (data) => authApi.postEmailVerification({ data }), +/** + * @description 이메일 회원가입을 위한 useMutation 커스텀 훅 + */ +const usePostEmailSignUp = () => { + return useMutation({ + mutationFn: (data) => authApi.postEmailSignUp(data), + onError: (error) => { + console.error("이메일 회원가입 실패:", error); + toast.error("회원가입에 실패했습니다."); + }, }); }; -export default usePostEmailVerification; \ No newline at end of file +export default usePostEmailSignUp; \ No newline at end of file diff --git a/src/apis/Auth/postKakaoAuth.ts b/src/apis/Auth/postKakaoAuth.ts index 2aaebd97..22676df0 100644 --- a/src/apis/Auth/postKakaoAuth.ts +++ b/src/apis/Auth/postKakaoAuth.ts @@ -1,10 +1,46 @@ +import { useRouter, useSearchParams } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; + +import { validateSafeRedirect } from "@/utils/authUtils"; + import { authApi, KakaoAuthResponse, KakaoAuthRequest } from "./api"; +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; + +/** + * @description 카카오 로그인을 위한 useMutation 커스텀 훅 + */ const usePostKakaoAuth = () => { + const { setAccessToken } = useAuthStore(); + const router = useRouter(); + const searchParams = useSearchParams(); + return useMutation({ - mutationFn: (data) => authApi.postKakaoAuth({ data }), + mutationFn: (data) => authApi.postKakaoAuth(data), + onSuccess: (data) => { + if (data.isRegistered) { + // 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장 + // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 + setAccessToken(data.accessToken); + + // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 + const redirectParam = searchParams.get("redirect"); + const safeRedirect = validateSafeRedirect(redirectParam); + + toast.success("로그인에 성공했습니다."); + router.replace(safeRedirect); + } else { + // 새로운 회원일 시 - 회원가입 페이지로 이동 + router.push(`/sign-up?token=${data.signUpToken}`); + } + }, + onError: () => { + toast.error("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요."); + router.push("/login"); + }, }); }; diff --git a/src/apis/Auth/postSignOut.ts b/src/apis/Auth/postSignOut.ts index 8d31dc93..f0674f53 100644 --- a/src/apis/Auth/postSignOut.ts +++ b/src/apis/Auth/postSignOut.ts @@ -1,11 +1,27 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { authApi, SignOutResponse, SignOutRequest } from "./api"; -const usePostSignOut = () => { - return useMutation({ - mutationFn: (data) => authApi.postSignOut({ data }), +import { authApi, SignOutResponse } from "./api"; + +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +/** + * @description 로그아웃을 위한 useMutation 커스텀 훅 + */ +const usePostLogout = () => { + const { clearAccessToken } = useAuthStore(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => authApi.postSignOut(), + onSuccess: () => { + // Zustand persist가 자동으로 localStorage에서 제거 + clearAccessToken(); + queryClient.clear(); + // 로그아웃 후 홈으로 리다이렉트 + window.location.href = "/"; + }, }); }; -export default usePostSignOut; \ No newline at end of file +export default usePostLogout; \ No newline at end of file diff --git a/src/apis/Auth/postSignUp.ts b/src/apis/Auth/postSignUp.ts index eadc9cb8..161e0b04 100644 --- a/src/apis/Auth/postSignUp.ts +++ b/src/apis/Auth/postSignUp.ts @@ -1,10 +1,20 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; + import { authApi, SignUpResponse, SignUpRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; + +/** + * @description 회원가입을 위한 useMutation 커스텀 훅 + */ const usePostSignUp = () => { return useMutation({ - mutationFn: (data) => authApi.postSignUp({ data }), + mutationFn: (data) => authApi.postSignUp(data), + onError: (error) => { + console.error("회원가입 실패:", error); + toast.error("회원가입에 실패했습니다."); + }, }); }; diff --git a/src/apis/Auth/server/index.ts b/src/apis/Auth/server/index.ts new file mode 100644 index 00000000..09d60f03 --- /dev/null +++ b/src/apis/Auth/server/index.ts @@ -0,0 +1 @@ +export { default as postReissueToken } from "./postReissueToken"; diff --git a/src/apis/Auth/server/postReissueToken.ts b/src/apis/Auth/server/postReissueToken.ts new file mode 100644 index 00000000..51c60ed1 --- /dev/null +++ b/src/apis/Auth/server/postReissueToken.ts @@ -0,0 +1,29 @@ +import { publicAxiosInstance } from "@/utils/axiosInstance"; + +import useAuthStore from "@/lib/zustand/useAuthStore"; + +/** + * @description 토큰 재발급 서버사이드 함수 + * axiosInstance의 interceptor에서 사용됨 + */ +const postReissueToken = async (): Promise => { + try { + const response = await publicAxiosInstance.post<{ accessToken: string }>("/auth/reissue"); + const newAccessToken = response.data.accessToken; + + if (!newAccessToken) { + throw new Error("재발급된 토큰이 유효하지 않습니다."); + } + + // 재발급 성공 시, 새로운 토큰을 Zustand 스토어에 저장 + useAuthStore.getState().setAccessToken(newAccessToken); + + return newAccessToken; + } catch (error) { + // 재발급 실패 시 스토어 상태 초기화 + useAuthStore.getState().clearAccessToken(); + throw error; + } +}; + +export default postReissueToken; diff --git a/src/apis/MyPage/api.ts b/src/apis/MyPage/api.ts index d6f5f956..8e1b7594 100644 --- a/src/apis/MyPage/api.ts +++ b/src/apis/MyPage/api.ts @@ -1,56 +1,68 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; +import { UserRole } from "@/types/mentor"; +import { BaseUserInfo } from "@/types/myInfo"; -export type InterestedRegionCountryResponse = void; +// --- 타입 정의 --- +export interface MenteeInfo extends BaseUserInfo { + role: UserRole.MENTEE; + interestedCountries: string[]; +} -export type InterestedRegionCountryRequest = Record; +export interface MentorInfo extends BaseUserInfo { + role: UserRole.MENTOR; + attendedUniversity: string; +} -export type ProfileResponse = Record; +export interface AdminInfo extends BaseUserInfo { + role: UserRole.ADMIN; + attendedUniversity: string; +} -export type ProfileRequest = Record; +export type MyInfoResponse = MenteeInfo | MentorInfo | AdminInfo; -export interface ProfileResponse { - likedUniversityCount: number; - nickname: string; - profileImageUrl: string; - role: string; - authType: string; - email: string; - likedPostCount: number; - likedMentorCount: number; - interestedCountries: string[]; -} +export type InterestedRegionCountryResponse = void; + +export type InterestedRegionCountryRequest = string[]; -export type PasswordResponse = void; +export interface ProfilePatchRequest { + nickname?: string; + file?: File; +} -export type PasswordRequest = Record; +export interface PasswordPatchRequest { + currentPassword: string; + newPassword: string; + newPasswordConfirmation: string; +} export const myPageApi = { - patchInterestedRegionCountry: async (params: { data?: InterestedRegionCountryRequest }): Promise => { - const res = await axiosInstance.patch( - `/my/interested-location`, params?.data - ); - return res.data; + getProfile: async (): Promise => { + const response: AxiosResponse = await axiosInstance.get("/my"); + return response.data; }, - patchProfile: async (params: { data?: ProfileRequest }): Promise => { - const res = await axiosInstance.patch( - `/my`, params?.data - ); + patchProfile: async (data: ProfilePatchRequest): Promise => { + const formData = new FormData(); + if (data.nickname) { + formData.append("nickname", data.nickname); + } + if (data.file) { + formData.append("file", data.file); + } + const res = await axiosInstance.patch("/my", formData); return res.data; }, - getProfile: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/my`, { params: params?.params } - ); + patchPassword: async (data: PasswordPatchRequest): Promise => { + const res = await axiosInstance.patch("/my/password", data); return res.data; }, - patchPassword: async (params: { data?: PasswordRequest }): Promise => { - const res = await axiosInstance.patch( - `/my/password`, params?.data + patchInterestedRegionCountry: async (data: InterestedRegionCountryRequest): Promise => { + const res = await axiosInstance.patch( + `/my/interested-location`, data ); return res.data; }, - }; \ No newline at end of file diff --git a/src/apis/MyPage/getProfile.ts b/src/apis/MyPage/getProfile.ts index 042c0767..cb38fa88 100644 --- a/src/apis/MyPage/getProfile.ts +++ b/src/apis/MyPage/getProfile.ts @@ -1,13 +1,33 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { myPageApi, ProfileResponse } from "./api"; +import { useMutationState, useQuery } from "@tanstack/react-query"; +import { myPageApi, MyInfoResponse } from "./api"; import { QueryKeys } from "../queryKeys"; -const useGetProfile = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.MyPage.profile, params], - queryFn: () => myPageApi.getProfile(params ? { params } : {}), +const useGetMyInfo = () => { + const queryResult = useQuery({ + queryKey: [QueryKeys.MyPage.profile], + queryFn: () => myPageApi.getProfile(), + // staleTime을 무한으로 설정하여 불필요한 자동 refetch를 방지합니다. + staleTime: Infinity, + gcTime: 1000 * 60 * 30, // 예: 30분 }); + + const pendingMutations = useMutationState({ + filters: { + mutationKey: [QueryKeys.MyPage.profile, "patch"], + status: "pending", + }, + select: (mutation) => { + return mutation.state.variables as Partial; + }, + }); + + const isOptimistic = pendingMutations.length > 0; + const pendingData = isOptimistic ? pendingMutations[0] : null; + + const displayData = isOptimistic ? { ...queryResult.data, ...pendingData } : queryResult.data; + + return { ...queryResult, data: displayData }; }; -export default useGetProfile; \ No newline at end of file +export default useGetMyInfo; \ No newline at end of file diff --git a/src/apis/MyPage/index.ts b/src/apis/MyPage/index.ts index 31a7cccf..b52be717 100644 --- a/src/apis/MyPage/index.ts +++ b/src/apis/MyPage/index.ts @@ -1,5 +1,5 @@ -export { myPageApi } from './api'; -export { default as getProfile } from './getProfile'; -export { default as patchInterestedRegionCountry } from './patchInterestedRegionCountry'; -export { default as patchPassword } from './patchPassword'; -export { default as patchProfile } from './patchProfile'; +export { myPageApi, type MyInfoResponse, type MenteeInfo, type MentorInfo, type AdminInfo, type ProfilePatchRequest, type PasswordPatchRequest } from './api'; +export { default as useGetMyInfo } from './getProfile'; +export { default as usePatchMyInfo } from './patchProfile'; +export { default as usePatchMyPassword } from './patchPassword'; +export { default as usePatchInterestedRegionCountry } from './patchInterestedRegionCountry'; diff --git a/src/apis/MyPage/patchInterestedRegionCountry.ts b/src/apis/MyPage/patchInterestedRegionCountry.ts index 0ea8b18f..3b03ccac 100644 --- a/src/apis/MyPage/patchInterestedRegionCountry.ts +++ b/src/apis/MyPage/patchInterestedRegionCountry.ts @@ -4,7 +4,7 @@ import { myPageApi, InterestedRegionCountryResponse, InterestedRegionCountryRequ const usePatchInterestedRegionCountry = () => { return useMutation({ - mutationFn: (data) => myPageApi.patchInterestedRegionCountry({ data }), + mutationFn: (data) => myPageApi.patchInterestedRegionCountry(data), }); }; diff --git a/src/apis/MyPage/patchPassword.ts b/src/apis/MyPage/patchPassword.ts index 8592b77e..aed9834d 100644 --- a/src/apis/MyPage/patchPassword.ts +++ b/src/apis/MyPage/patchPassword.ts @@ -1,11 +1,30 @@ +import { useRouter } from "next/navigation"; import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { myPageApi, PasswordResponse, PasswordRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { myPageApi, PasswordPatchRequest } from "./api"; +import { QueryKeys } from "../queryKeys"; +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePatchPassword = () => { - return useMutation({ - mutationFn: (data) => myPageApi.patchPassword({ data }), +const usePatchMyPassword = () => { + const queryClient = useQueryClient(); + const router = useRouter(); + const { clearAccessToken } = useAuthStore(); + + return useMutation, PasswordPatchRequest>({ + mutationKey: [QueryKeys.MyPage.password, "patch"], + mutationFn: (data) => myPageApi.patchPassword(data), + onSuccess: () => { + clearAccessToken(); + queryClient.clear(); + toast.success("비밀번호가 성공적으로 변경되었습니다."); + router.replace("/"); + }, + onError: (error) => { + const errorMessage = error.response?.data?.message; + toast.error(errorMessage || "비밀번호 변경에 실패했습니다. 다시 시도해주세요."); + }, }); }; -export default usePatchPassword; \ No newline at end of file +export default usePatchMyPassword; \ No newline at end of file diff --git a/src/apis/MyPage/patchProfile.ts b/src/apis/MyPage/patchProfile.ts index 20204ad8..cacbf78f 100644 --- a/src/apis/MyPage/patchProfile.ts +++ b/src/apis/MyPage/patchProfile.ts @@ -1,11 +1,28 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { myPageApi, ProfileResponse, ProfileRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { myPageApi, ProfilePatchRequest } from "./api"; +import { QueryKeys } from "../queryKeys"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePatchProfile = () => { - return useMutation({ - mutationFn: (data) => myPageApi.patchProfile({ data }), +const usePatchMyInfo = () => { + const queryClient = useQueryClient(); + + return useMutation, ProfilePatchRequest>({ + mutationKey: [QueryKeys.MyPage.profile, "patch"], + mutationFn: (data) => myPageApi.patchProfile(data), + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: [QueryKeys.MyPage.profile], + }); + }, + onSuccess: () => { + toast.success("프로필이 성공적으로 수정되었습니다."); + }, + onError: (error) => { + const errorMessage = error.response?.data?.message; + toast.error(errorMessage || "프로필 수정에 실패했습니다. 다시 시도해주세요."); + }, }); }; -export default usePatchProfile; \ No newline at end of file +export default usePatchMyInfo; \ No newline at end of file diff --git a/src/apis/community/api.ts b/src/apis/community/api.ts index 52eaec57..8ec61bc2 100644 --- a/src/apis/community/api.ts +++ b/src/apis/community/api.ts @@ -1,4 +1,19 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; +import { + Post, + PostCreateRequest, + PostIdResponse, + PostUpdateRequest, + PostLikeResponse, + CommentCreateRequest, + CommentIdResponse +} from "@/types/community"; + +// QueryKeys for community domain +export const CommunityQueryKeys = { + posts: "posts", +} as const; export interface BoardListResponse { 0: string; @@ -26,172 +41,107 @@ export interface BoardResponse { 3: BoardResponse3; } -export interface CommentResponse { - id: number; +// Delete response types +export interface DeletePostResponse { + message: string; + postId: number; } -export interface UpdateCommentResponse { - id: number; -} - -export type UpdateCommentRequest = Record; - -export interface CreateCommentResponse { - id: number; -} - -export type CreateCommentRequest = Record; - -export interface PostResponse { - id: number; -} - -export interface UpdatePostResponse { - id: number; -} - -export type UpdatePostRequest = Record; - -export interface CreatePostResponse { - id: number; -} - -export type CreatePostRequest = Record; - -export interface PostDetailResponsePostFindPostImageResponsesItem { - id: number; - imageUrl: string; -} - -export interface PostDetailResponsePostFindCommentResponsesItem { - id: number; - parentId: null | number; - content: string; - isOwner: boolean; - createdAt: string; - updatedAt: string; - postFindSiteUserResponse: PostDetailResponsePostFindCommentResponsesItemPostFindSiteUserResponse; -} - -export interface PostDetailResponsePostFindCommentResponsesItemPostFindSiteUserResponse { - id: number; - nickname: string; - profileImageUrl: string; -} - -export interface PostDetailResponsePostFindSiteUserResponse { - id: number; - nickname: string; - profileImageUrl: string; -} - -export interface PostDetailResponsePostFindBoardResponse { - code: string; - koreanName: string; -} - -export interface PostDetailResponse { - id: number; - title: string; - content: string; - isQuestion: boolean; - likeCount: number; - viewCount: number; - commentCount: number; - postCategory: string; - isOwner: boolean; - isLiked: boolean; - createdAt: string; - updatedAt: string; - postFindBoardResponse: PostDetailResponsePostFindBoardResponse; - postFindSiteUserResponse: PostDetailResponsePostFindSiteUserResponse; - postFindCommentResponses: PostDetailResponsePostFindCommentResponsesItem[]; - postFindPostImageResponses: PostDetailResponsePostFindPostImageResponsesItem[]; -} - -export interface LikePostResponse { - likeCount: number; - isLiked: boolean; -} - -export type LikePostRequest = Record; +// Re-export types from @/types/community for convenience +export type { + Post, + PostCreateRequest, + PostIdResponse, + PostUpdateRequest, + PostLikeResponse, + CommentCreateRequest, + CommentIdResponse +}; export const communityApi = { - getBoardList: async (params: { params?: Record }): Promise => { + getBoardList: async (params?: Record): Promise => { const res = await axiosInstance.get( - `/boards`, { params: params?.params } + `/boards`, { params } ); return res.data; }, - getBoard: async (params: { boardCode: string | number, params?: Record }): Promise => { + getBoard: async (boardCode: string, params?: Record): Promise => { const res = await axiosInstance.get( - `/boards/${params.boardCode}`, { params: params?.params } + `/boards/${boardCode}`, { params } ); return res.data; }, - deleteComment: async (params: { commentId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/comments/${params.commentId}` - ); - return res.data; + getPostDetail: async (postId: number): Promise => { + const response: AxiosResponse = await axiosInstance.get(`/posts/${postId}`); + return response.data; }, - patchUpdateComment: async (params: { commentId: string | number, data?: UpdateCommentRequest }): Promise => { - const res = await axiosInstance.patch( - `/comments/${params.commentId}`, params?.data + createPost: async (request: PostCreateRequest): Promise => { + const convertedRequest: FormData = new FormData(); + convertedRequest.append( + "postCreateRequest", + new Blob([JSON.stringify(request.postCreateRequest)], { type: "application/json" }) ); - return res.data; + request.file.forEach((file) => { + convertedRequest.append("file", file); + }); + + const response: AxiosResponse = await axiosInstance.post(`/posts`, convertedRequest, { + headers: { "Content-Type": "multipart/form-data" }, + }); + + return { + ...response.data, + boardCode: request.postCreateRequest.boardCode, + }; }, - postCreateComment: async (params: { data?: CreateCommentRequest }): Promise => { - const res = await axiosInstance.post( - `/comments`, params?.data + updatePost: async (postId: number, request: PostUpdateRequest): Promise => { + const convertedRequest: FormData = new FormData(); + convertedRequest.append( + "postUpdateRequest", + new Blob([JSON.stringify(request.postUpdateRequest)], { type: "application/json" }), ); - return res.data; + request.file.forEach((file) => { + convertedRequest.append("file", file); + }); + + const response: AxiosResponse = await axiosInstance.patch(`/posts/${postId}`, convertedRequest, { + headers: { "Content-Type": "multipart/form-data" }, + }); + return response.data; }, - deletePost: async (params: { postId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/posts/${params.postId}` - ); - return res.data; + deletePost: async (postId: number): Promise> => { + return axiosInstance.delete(`/posts/${postId}`); }, - patchUpdatePost: async (params: { postId: string | number, data?: UpdatePostRequest }): Promise => { - const res = await axiosInstance.patch( - `/posts/${params.postId}`, params?.data - ); - return res.data; + likePost: async (postId: number): Promise => { + const response: AxiosResponse = await axiosInstance.post(`/posts/${postId}/like`); + return response.data; }, - postCreatePost: async (params: { data?: CreatePostRequest }): Promise => { - const res = await axiosInstance.post( - `/posts`, params?.data - ); - return res.data; + unlikePost: async (postId: number): Promise => { + const response: AxiosResponse = await axiosInstance.delete(`/posts/${postId}/like`); + return response.data; }, - getPostDetail: async (params: { postId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/posts/${params.postId}`, { params: params?.params } - ); - return res.data; + createComment: async (request: CommentCreateRequest): Promise => { + const response: AxiosResponse = await axiosInstance.post(`/comments`, request); + return response.data; }, - postLikePost: async (params: { postId: string | number, data?: LikePostRequest }): Promise => { - const res = await axiosInstance.post( - `/posts/${params.postId}/like`, params?.data - ); - return res.data; + deleteComment: async (commentId: number): Promise => { + const response: AxiosResponse = await axiosInstance.delete(`/comments/${commentId}`); + return response.data; }, - deleteLikePost: async (params: { postId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/posts/${params.postId}/like` + updateComment: async (commentId: number, data: { content: string }): Promise => { + const res = await axiosInstance.patch( + `/comments/${commentId}`, data ); return res.data; }, - }; \ No newline at end of file diff --git a/src/apis/community/deleteComment.ts b/src/apis/community/deleteComment.ts index 6bea8c7e..0741e537 100644 --- a/src/apis/community/deleteComment.ts +++ b/src/apis/community/deleteComment.ts @@ -1,10 +1,30 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, CommentResponse, CommentRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, CommentIdResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; +interface DeleteCommentRequest { + commentId: number; + postId: number; +} + +/** + * @description 댓글 삭제를 위한 useMutation 커스텀 훅 + */ const useDeleteComment = () => { - return useMutation({ - mutationFn: (variables) => communityApi.deleteComment(variables), + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ commentId }) => communityApi.deleteComment(commentId), + onSuccess: (data, variables) => { + // 해당 게시글 상세 쿼리를 무효화하여 댓글 목록 갱신 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] }); + toast.success("댓글이 삭제되었습니다."); + }, + onError: (error) => { + console.error("댓글 삭제 실패:", error); + toast.error("댓글 삭제에 실패했습니다."); + }, }); }; diff --git a/src/apis/community/deleteLikePost.ts b/src/apis/community/deleteLikePost.ts index 63036f50..1dac62ef 100644 --- a/src/apis/community/deleteLikePost.ts +++ b/src/apis/community/deleteLikePost.ts @@ -1,11 +1,25 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, LikePostResponse, LikePostRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, PostLikeResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const useDeleteLikePost = () => { - return useMutation({ - mutationFn: (variables) => communityApi.deleteLikePost(variables), +/** + * @description 게시글 좋아요 취소를 위한 useMutation 커스텀 훅 + */ +const useDeleteLike = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: communityApi.unlikePost, + onSuccess: (data, postId) => { + // 해당 게시글 상세 쿼리를 무효화하여 최신 데이터 반영 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, postId] }); + }, + onError: (error) => { + console.error("게시글 좋아요 취소 실패:", error); + toast.error("좋아요 취소 처리에 실패했습니다."); + }, }); }; -export default useDeleteLikePost; \ No newline at end of file +export default useDeleteLike; \ No newline at end of file diff --git a/src/apis/community/deletePost.ts b/src/apis/community/deletePost.ts index 1b194b65..7290d4e9 100644 --- a/src/apis/community/deletePost.ts +++ b/src/apis/community/deletePost.ts @@ -1,10 +1,32 @@ -import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, PostResponse, PostRequest } from "./api"; +import { useRouter } from "next/navigation"; +import { AxiosResponse, AxiosError } from "axios"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, DeletePostResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; +/** + * @description 게시글 삭제를 위한 useMutation 커스텀 훅 + */ const useDeletePost = () => { - return useMutation({ - mutationFn: (variables) => communityApi.deletePost(variables), + const router = useRouter(); + const queryClient = useQueryClient(); + + return useMutation, AxiosError, number>({ + mutationFn: communityApi.deletePost, + onSuccess: () => { + // 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여 + // 게시글 목록을 다시 불러오도록 합니다. + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + + toast.success("게시글이 성공적으로 삭제되었습니다."); + + // 게시글 목록 페이지 이동 + router.replace("/community/FREE"); + }, + onError: (error) => { + console.error("게시글 삭제 실패:", error); + toast.error("게시글 삭제에 실패했습니다. 잠시 후 다시 시도해주세요."); + }, }); }; diff --git a/src/apis/community/getPostDetail.ts b/src/apis/community/getPostDetail.ts index ba5cc61a..3a71fda8 100644 --- a/src/apis/community/getPostDetail.ts +++ b/src/apis/community/getPostDetail.ts @@ -1,12 +1,14 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { communityApi, PostDetailResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { communityApi, CommunityQueryKeys, Post } from "./api"; -const useGetPostDetail = (postId: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.community.postDetail, postId, params], - queryFn: () => communityApi.getPostDetail({ postId, params }), +/** + * @description 게시글 상세 조회를 위한 useQuery 커스텀 훅 + */ +const useGetPostDetail = (postId: number) => { + return useQuery({ + queryKey: [CommunityQueryKeys.posts, postId], + queryFn: () => communityApi.getPostDetail(postId), enabled: !!postId, }); }; diff --git a/src/apis/community/patchUpdatePost.ts b/src/apis/community/patchUpdatePost.ts index acbdf578..e36566b2 100644 --- a/src/apis/community/patchUpdatePost.ts +++ b/src/apis/community/patchUpdatePost.ts @@ -1,11 +1,32 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, UpdatePostResponse, UpdatePostRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, PostIdResponse, PostUpdateRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePatchUpdatePost = () => { - return useMutation({ - mutationFn: (variables) => communityApi.patchUpdatePost(variables), +interface UpdatePostVariables { + postId: number; + data: PostUpdateRequest; +} + +/** + * @description 게시글 수정을 위한 useMutation 커스텀 훅 + */ +const useUpdatePost = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ postId, data }) => communityApi.updatePost(postId, data), + onSuccess: (result, variables) => { + // 해당 게시글 상세 쿼리와 목록 쿼리를 무효화 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] }); + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + toast.success("게시글이 수정되었습니다."); + }, + onError: (error) => { + console.error("게시글 수정 실패:", error); + toast.error("게시글 수정에 실패했습니다."); + }, }); }; -export default usePatchUpdatePost; \ No newline at end of file +export default useUpdatePost; \ No newline at end of file diff --git a/src/apis/community/postCreateComment.ts b/src/apis/community/postCreateComment.ts index 215cd41e..a27ccfc5 100644 --- a/src/apis/community/postCreateComment.ts +++ b/src/apis/community/postCreateComment.ts @@ -1,11 +1,26 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, CreateCommentResponse, CreateCommentRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, CommentCreateRequest, CommentIdResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostCreateComment = () => { - return useMutation({ - mutationFn: (data) => communityApi.postCreateComment({ data }), +/** + * @description 댓글 생성을 위한 useMutation 커스텀 훅 + */ +const useCreateComment = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: communityApi.createComment, + onSuccess: (data, variables) => { + // 해당 게시글 상세 쿼리를 무효화하여 댓글 목록 갱신 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] }); + toast.success("댓글이 등록되었습니다."); + }, + onError: (error) => { + console.error("댓글 생성 실패:", error); + toast.error("댓글 등록에 실패했습니다."); + }, }); }; -export default usePostCreateComment; \ No newline at end of file +export default useCreateComment; \ No newline at end of file diff --git a/src/apis/community/postCreatePost.ts b/src/apis/community/postCreatePost.ts index e83b02c4..ea9e3bfb 100644 --- a/src/apis/community/postCreatePost.ts +++ b/src/apis/community/postCreatePost.ts @@ -1,11 +1,59 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, CreatePostResponse, CreatePostRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, PostCreateRequest, PostIdResponse } from "./api"; +import useAuthStore from "@/lib/zustand/useAuthStore"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostCreatePost = () => { - return useMutation({ - mutationFn: (data) => communityApi.postCreatePost({ data }), +/** + * @description ISR 페이지를 revalidate하는 함수 + * @param boardCode - 게시판 코드 + * @param accessToken - 사용자 인증 토큰 + */ +const revalidateCommunityPage = async (boardCode: string, accessToken: string) => { + try { + if (!accessToken) { + console.warn("Revalidation skipped: No access token available"); + return; + } + + await fetch("/api/revalidate", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ boardCode }), + }); + } catch (error) { + console.error("Revalidate failed:", error); + } +}; + +/** + * @description 게시글 생성을 위한 useMutation 커스텀 훅 + */ +const useCreatePost = () => { + const queryClient = useQueryClient(); + const { accessToken } = useAuthStore(); + + return useMutation({ + mutationFn: communityApi.createPost, + onSuccess: async (data) => { + // 게시글 목록 쿼리를 무효화하여 최신 목록 반영 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); + + // ISR 페이지 revalidate (사용자 인증 토큰 사용) + if (accessToken) { + await revalidateCommunityPage(data.boardCode, accessToken); + } + + toast.success("게시글이 등록되었습니다."); + }, + onError: (error) => { + console.error("게시글 생성 실패:", error); + toast.error("게시글 등록에 실패했습니다."); + }, }); }; -export default usePostCreatePost; \ No newline at end of file +export default useCreatePost; \ No newline at end of file diff --git a/src/apis/community/postLikePost.ts b/src/apis/community/postLikePost.ts index f88f4c4c..2e95b3e9 100644 --- a/src/apis/community/postLikePost.ts +++ b/src/apis/community/postLikePost.ts @@ -1,11 +1,25 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { communityApi, LikePostResponse, LikePostRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys, PostLikeResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostLikePost = () => { - return useMutation({ - mutationFn: (variables) => communityApi.postLikePost(variables), +/** + * @description 게시글 좋아요를 위한 useMutation 커스텀 훅 + */ +const usePostLike = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: communityApi.likePost, + onSuccess: (data, postId) => { + // 해당 게시글 상세 쿼리를 무효화하여 최신 데이터 반영 + queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, postId] }); + }, + onError: (error) => { + console.error("게시글 좋아요 실패:", error); + toast.error("좋아요 처리에 실패했습니다."); + }, }); }; -export default usePostLikePost; \ No newline at end of file +export default usePostLike; \ No newline at end of file diff --git a/src/app/login/LoginContent.tsx b/src/app/login/LoginContent.tsx index 7277369d..4c83d0b9 100644 --- a/src/app/login/LoginContent.tsx +++ b/src/app/login/LoginContent.tsx @@ -11,7 +11,7 @@ import { appleLogin, kakaoLogin } from "@/utils/authUtils"; import useInputHandler from "./_hooks/useInputHandler"; -import usePostEmailAuth from "@/api/auth/client/usePostEmailAuth"; +import { usePostEmailAuth } from "@/apis/Auth"; import { toast } from "@/lib/zustand/useToastStore"; import { IconSolidConnectionFullBlackLogo } from "@/public/svgs"; import { IconAppleLogo, IconEmailIcon, IconKakaoLogo } from "@/public/svgs/auth"; diff --git a/src/app/login/apple/callback/AppleLoginCallbackPage.tsx b/src/app/login/apple/callback/AppleLoginCallbackPage.tsx index 06a4db17..96dde829 100644 --- a/src/app/login/apple/callback/AppleLoginCallbackPage.tsx +++ b/src/app/login/apple/callback/AppleLoginCallbackPage.tsx @@ -5,7 +5,7 @@ import { useEffect } from "react"; import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; -import usePostAppleAuth from "@/api/auth/client/usePostAppleAuth"; +import { usePostAppleAuth } from "@/apis/Auth"; const AppleLoginCallbackPage = () => { const searchParams = useSearchParams(); diff --git a/src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx b/src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx index 9305998d..055967ab 100644 --- a/src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx +++ b/src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx @@ -5,7 +5,7 @@ import { useEffect } from "react"; import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; -import usePostKakaoAuth from "@/api/auth/client/usePostKakaoAuth"; +import { usePostKakaoAuth } from "@/apis/Auth"; const KakaoLoginCallbackPage = () => { const searchParams = useSearchParams(); diff --git a/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx b/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx index 6faafa1a..60f21621 100644 --- a/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx +++ b/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx @@ -15,7 +15,7 @@ import ReportPanel from "../../../../../../components/ui/ReportPanel"; import { UserRole } from "@/types/mentor"; import useGetPartnerInfo from "@/api/chat/clients/useGetPartnerInfo"; -import useGetMyInfo from "@/api/my/client/useGetMyInfo"; +import { useGetMyInfo } from "@/apis/MyPage"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { IconAlert, IconAlertSubC, IconDirectionRight, IconSetting } from "@/public/svgs/mentor"; diff --git a/src/app/mentor/chat/_ui/ChatPageClient/index.tsx b/src/app/mentor/chat/_ui/ChatPageClient/index.tsx index 6b9fedca..683969f2 100644 --- a/src/app/mentor/chat/_ui/ChatPageClient/index.tsx +++ b/src/app/mentor/chat/_ui/ChatPageClient/index.tsx @@ -9,7 +9,7 @@ import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { UserRole } from "@/types/mentor"; import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; -import useGetMyInfo from "@/api/my/client/useGetMyInfo"; +import { useGetMyInfo } from "@/apis/MyPage"; import { IconSearchBlue, IconSolidConnentionLogo } from "@/public/svgs/mentor"; const ChatPageClient = () => { diff --git a/src/app/my/_ui/MyProfileContent/index.tsx b/src/app/my/_ui/MyProfileContent/index.tsx index fa08cc82..810e36a3 100644 --- a/src/app/my/_ui/MyProfileContent/index.tsx +++ b/src/app/my/_ui/MyProfileContent/index.tsx @@ -8,9 +8,8 @@ import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { UserRole } from "@/types/mentor"; -import useDeleteUserAccount from "@/api/auth/client/useDeleteUserAccount"; -import usePostLogout from "@/api/auth/client/usePostLogout"; -import useGetMyInfo from "@/api/my/client/useGetMyInfo"; +import { useGetMyInfo } from "@/apis/MyPage"; +import { useDeleteUserAccount, usePostLogout } from "@/apis/Auth"; import { toast } from "@/lib/zustand/useToastStore"; import { IconLikeFill } from "@/public/svgs/mentor"; import { diff --git a/src/app/my/favorite/_ui/FavoriteContent/index.tsx b/src/app/my/favorite/_ui/FavoriteContent/index.tsx index a3acdb2c..ff10fc46 100644 --- a/src/app/my/favorite/_ui/FavoriteContent/index.tsx +++ b/src/app/my/favorite/_ui/FavoriteContent/index.tsx @@ -8,7 +8,7 @@ import useSelectUniversities from "./_hooks/useSelectUniversities"; import useSortedUniversities from "./_hooks/useSortedUniversities"; import FavoriteDropDown from "./_ui/FavoriteDropDown"; -import useGetMyInfo from "@/api/my/client/useGetMyInfo"; +import { useGetMyInfo } from "@/apis/MyPage"; // 필터 타입 Enum export enum filterType { diff --git a/src/app/my/match/_ui/MatchContent/index.tsx b/src/app/my/match/_ui/MatchContent/index.tsx index 32a4d445..f70c2065 100644 --- a/src/app/my/match/_ui/MatchContent/index.tsx +++ b/src/app/my/match/_ui/MatchContent/index.tsx @@ -8,7 +8,7 @@ import MentorChatCard from "@/components/mentor/MentorChatCard"; import { UserRole } from "@/types/mentor"; import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; -import useGetMyInfo from "@/api/my/client/useGetMyInfo"; +import { useGetMyInfo } from "@/apis/MyPage"; const MatchContent = () => { const { data: myInfo = {} } = useGetMyInfo(); diff --git a/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts b/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts index 82fbfe98..08f38094 100644 --- a/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts +++ b/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts @@ -3,8 +3,7 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; -import useGetMyInfo, { MyInfoResponse } from "@/api/my/client/useGetMyInfo"; -import usePatchMyInfo from "@/api/my/client/usePatchMyInfo"; +import { useGetMyInfo, usePatchMyInfo, type MyInfoResponse } from "@/apis/MyPage"; import { zodResolver } from "@hookform/resolvers/zod"; // Zod 스키마 정의 - 닉네임과 이미지 diff --git a/src/app/my/password/_ui/PasswordContent/index.tsx b/src/app/my/password/_ui/PasswordContent/index.tsx index d9d0a8d6..9b9aa084 100644 --- a/src/app/my/password/_ui/PasswordContent/index.tsx +++ b/src/app/my/password/_ui/PasswordContent/index.tsx @@ -8,7 +8,7 @@ import { z } from "zod"; import PasswordInput from "./_ui/PasswordInput"; -import usePatchMyPassword from "@/api/my/client/usePatchMyPassword"; +import { usePatchMyPassword } from "@/apis/MyPage"; import { zodResolver } from "@hookform/resolvers/zod"; export const changePasswordSchema = z diff --git a/src/app/sign-up/email/EmailSignUpForm.tsx b/src/app/sign-up/email/EmailSignUpForm.tsx index e29a0728..832db620 100644 --- a/src/app/sign-up/email/EmailSignUpForm.tsx +++ b/src/app/sign-up/email/EmailSignUpForm.tsx @@ -8,7 +8,7 @@ import { Input } from "@/components/ui/Inputa"; import { Label } from "@/components/ui/Label"; import { Progress } from "@/components/ui/Progress"; -import usePostEmailSignUp from "@/api/auth/client/usePostEmailSignUp"; +import { usePostEmailSignUp } from "@/apis/Auth"; import { toast } from "@/lib/zustand/useToastStore"; import { IconCheckBlue, IconExpRed, IconEyeOff, IconEyeOn } from "@/public/svgs/ui"; diff --git a/src/components/layout/ReissueProvider/index.tsx b/src/components/layout/ReissueProvider/index.tsx index 0807ef65..109dedf3 100644 --- a/src/components/layout/ReissueProvider/index.tsx +++ b/src/components/layout/ReissueProvider/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from "react"; -import postReissueToken from "@/api/auth/server/postReissueToken"; +import { postReissueToken } from "@/apis/Auth"; import useAuthStore from "@/lib/zustand/useAuthStore"; interface ReissueProviderProps { diff --git a/src/components/login/signup/SignupSurvey.tsx b/src/components/login/signup/SignupSurvey.tsx index e3265da0..25aa787b 100644 --- a/src/components/login/signup/SignupSurvey.tsx +++ b/src/components/login/signup/SignupSurvey.tsx @@ -13,7 +13,7 @@ import SignupRegionScreen from "./SignupRegionScreen"; import { PreparationStatus, SignUpRequest } from "@/types/auth"; import { RegionKo } from "@/types/university"; -import usePostSignUp from "@/api/auth/client/usePostSignUp"; +import { usePostSignUp } from "@/apis/Auth"; import useUploadProfileImagePublic from "@/api/file/client/useUploadProfileImagePublic"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; diff --git a/src/utils/axiosInstance.ts b/src/utils/axiosInstance.ts index e8dffb10..7080b763 100644 --- a/src/utils/axiosInstance.ts +++ b/src/utils/axiosInstance.ts @@ -1,6 +1,6 @@ import axios, { AxiosError, AxiosInstance } from "axios"; -import postReissueToken from "@/api/auth/server/postReissueToken"; +import { postReissueToken } from "@/apis/Auth"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; From 4a113dc7b10dcfbc17a64d5614a2d3d9e540e203 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 22:02:10 +0900 Subject: [PATCH 04/11] refactor: complete API migration from src/api to src/apis - Migrate all 13 domains (63+ files) to src/apis - Update all component imports from @/api to @/apis - Add domain-specific QueryKeys (ChatQueryKeys, MentorQueryKeys, etc.) - Implement infinite scroll, optimistic updates, and prefetch patterns - Add SSR support with getPostListServer for community - Remove legacy src/api folder - Update PRD documentation to reflect completion Domains migrated: - auth, my/MyPage, community, chat - mentor/mentee/mentors, news, Scores - applications, reports, image-upload --- docs/api-migration-prd.md | 80 +++--- src/api/applications/client/queryKeys.ts | 3 - .../client/useGetApplicationsList.ts | 38 --- .../useGetCompetitorsApplicationList.ts | 38 --- .../client/usePostSubmitApplication.ts | 59 ---- src/api/auth/client/useDeleteUserAccount.ts | 35 --- src/api/auth/client/usePostAppleAuth.ts | 68 ----- src/api/auth/client/usePostEmailAuth.ts | 48 ---- src/api/auth/client/usePostEmailSignUp.ts | 33 --- src/api/auth/client/usePostKakaoAuth.ts | 69 ----- src/api/auth/client/usePostLogout.ts | 26 -- src/api/auth/client/usePostSignUp.ts | 40 --- src/api/auth/server/postReissueToken.ts | 24 -- src/api/auth/useLogin.ts | 0 src/api/boards/clients/QueryKeys.ts | 3 - src/api/boards/clients/useGetPostList.ts | 39 --- src/api/chat/clients/queryKey.ts | 5 - src/api/chat/clients/useGetChatHistories.ts | 68 ----- src/api/chat/clients/useGetChatRooms.ts | 25 -- src/api/chat/clients/useGetPartnerInfo.ts | 23 -- src/api/chat/clients/usePutChatRead.ts | 29 -- src/api/community/client/queryKey.ts | 3 - src/api/community/client/useCreateComment.ts | 42 --- src/api/community/client/useCreatePost.ts | 90 ------ src/api/community/client/useDeleteComment.ts | 47 ---- src/api/community/client/useDeleteLike.ts | 41 --- src/api/community/client/useDeletePost.ts | 63 ----- src/api/community/client/useGetPostDetail.ts | 32 --- src/api/community/client/usePostLike.ts | 41 --- src/api/community/client/useUpdatePost.ts | 60 ---- .../client/useUploadProfileImagePublic.ts | 38 --- src/api/mentee/client/queryKey.ts | 3 - .../mentee/client/useGetApplyMentoringList.ts | 65 ----- .../client/usePatchMenteeCheckMentorings.ts | 27 -- .../mentee/client/usePostApplyMentoring.ts | 34 --- src/api/mentor/client/queryKey.ts | 5 - .../mentor/client/useGetMentorMyProfile.ts | 25 -- src/api/mentor/client/useGetMentoringList.ts | 39 --- .../client/useGetMentoringUncheckedCount.ts | 28 -- .../mentor/client/usePatchApprovalStatus.ts | 74 ----- .../client/usePatchMentorCheckMentorings.ts | 35 --- .../mentor/client/usePostMentorApplication.ts | 51 ---- .../mentor/client/usePutMyMentorProfile.ts | 37 --- src/api/mentors/client/queryKey.ts | 8 - src/api/mentors/client/useGetMentorDetail.ts | 26 -- src/api/mentors/client/useGetMentorList.ts | 59 ---- src/api/my/client/queryKey.ts | 3 - src/api/my/client/useGetMyInfo.ts | 63 ----- src/api/my/client/usePatchMyInfo.ts | 50 ---- src/api/my/client/usePatchMyPassword.ts | 45 --- src/api/news/client/queryKey.ts | 5 - src/api/news/client/useDeleteArticle.ts | 66 ----- src/api/news/client/useDeleteArticleLike.ts | 74 ----- src/api/news/client/useGetArticleList.ts | 38 --- src/api/news/client/usePostAddArticle.ts | 79 ------ src/api/news/client/usePostArticleLike.ts | 74 ----- src/api/news/client/usePutModifyArticle.ts | 83 ------ src/api/reports/client/usePostReport.ts | 37 --- src/api/score/client/queryKey.ts | 4 - src/api/score/client/useGetMyGpaScore.ts | 26 -- .../score/client/useGetMyLanguageTestScore.ts | 27 -- src/api/score/client/usePostGpaScore.ts | 45 --- .../score/client/usePostLanguageTestScore.ts | 54 ---- src/apis/Auth/api.ts | 2 +- src/apis/Auth/deleteAccount.ts | 4 +- src/apis/Auth/postAppleAuth.ts | 4 +- src/apis/Auth/postEmailLogin.ts | 4 +- src/apis/Auth/postEmailVerification.ts | 4 +- src/apis/Auth/postKakaoAuth.ts | 4 +- src/apis/Auth/postSignOut.ts | 4 +- src/apis/Auth/postSignUp.ts | 4 +- src/apis/MyPage/api.ts | 12 +- src/apis/MyPage/getProfile.ts | 16 +- src/apis/MyPage/index.ts | 18 +- .../MyPage/patchInterestedRegionCountry.ts | 6 +- src/apis/MyPage/patchPassword.ts | 9 +- src/apis/MyPage/patchProfile.ts | 8 +- src/apis/Scores/api.ts | 113 ++++---- src/apis/Scores/getGpaList.ts | 19 +- src/apis/Scores/getLanguageTestList.ts | 19 +- src/apis/Scores/index.ts | 17 +- src/apis/Scores/postCreateGpa.ts | 28 +- src/apis/Scores/postCreateLanguageTest.ts | 28 +- src/apis/applications/api.ts | 165 ++--------- src/apis/applications/getApplicants.ts | 35 ++- src/apis/applications/index.ts | 9 +- .../applications/postSubmitApplication.ts | 37 ++- src/apis/chat/api.ts | 82 +++--- src/apis/chat/getChatMessages.ts | 48 +++- src/apis/chat/getChatPartner.ts | 17 +- src/apis/chat/getChatRooms.ts | 20 +- src/apis/chat/index.ts | 11 +- src/apis/chat/putReadChatRoom.ts | 24 +- src/apis/community/api.ts | 59 ++-- src/apis/community/deleteComment.ts | 8 +- src/apis/community/deleteLikePost.ts | 8 +- src/apis/community/deletePost.ts | 11 +- src/apis/community/getPostDetail.ts | 6 +- src/apis/community/getPostList.ts | 28 ++ src/apis/community/index.ts | 38 ++- src/apis/community/patchUpdatePost.ts | 8 +- src/apis/community/postCreateComment.ts | 8 +- src/apis/community/postCreatePost.ts | 12 +- src/apis/community/postLikePost.ts | 8 +- .../community/server.ts} | 10 +- src/apis/image-upload/api.ts | 63 +++-- src/apis/image-upload/index.ts | 16 +- .../postUploadProfileImageBeforeSignup.ts | 19 +- src/apis/mentor/api.ts | 260 +++++++----------- src/apis/mentor/getAppliedMentorings.ts | 48 +++- src/apis/mentor/getMentorDetail.ts | 17 +- src/apis/mentor/getMentorList.ts | 44 ++- src/apis/mentor/getMyMentorPage.ts | 17 +- src/apis/mentor/getReceivedMentorings.ts | 27 +- .../mentor/getUnconfirmedMentoringCount.ts | 20 +- src/apis/mentor/index.ts | 41 ++- src/apis/mentor/patchConfirmMentoring.ts | 23 +- src/apis/mentor/patchMenteeCheckMentorings.ts | 14 + src/apis/mentor/patchMentoringStatus.ts | 56 +++- src/apis/mentor/postApplyMentoring.ts | 20 +- src/apis/mentor/postMentorApplication.ts | 18 ++ src/apis/mentor/putUpdateMyMentorPage.ts | 23 +- src/apis/news/api.ts | 141 ++++++---- src/apis/news/deleteLikeNews.ts | 52 +++- src/apis/news/deleteNews.ts | 51 +++- src/apis/news/getNewsList.ts | 25 +- src/apis/news/index.ts | 23 +- src/apis/news/postCreateNews.ts | 58 +++- src/apis/news/postLikeNews.ts | 52 +++- src/apis/news/putUpdateNews.ts | 60 +++- src/apis/reports/api.ts | 24 +- src/apis/reports/index.ts | 3 +- src/apis/reports/postReport.ts | 23 +- .../[boardCode]/CommunityPageContent.tsx | 2 +- .../[boardCode]/[postId]/CommentInput.tsx | 2 +- .../[boardCode]/[postId]/CommentSection.tsx | 2 +- .../[boardCode]/[postId]/Content.tsx | 3 +- .../[boardCode]/[postId]/KebabMenu.tsx | 2 +- .../[boardCode]/[postId]/PostPageContent.tsx | 2 +- .../[postId]/modify/PostModifyContent.tsx | 2 +- .../[postId]/modify/PostModifyForm.tsx | 2 +- .../community/[boardCode]/create/PostForm.tsx | 2 +- src/app/community/[boardCode]/page.tsx | 7 +- src/app/login/LoginContent.tsx | 8 + .../_ui/MentorArticle/_hooks/useLikeToggle.ts | 3 +- .../[id]/_ui/MentorDetialContent/index.tsx | 5 +- .../MentorClient/_ui/MenteePageTabs/index.tsx | 4 +- .../_hooks/usePrefetchMentorFindTab.ts | 2 +- .../_ui/MentorFindSection/index.tsx | 2 +- .../_ui/ApplicantListSection/index.tsx | 2 +- .../MentorPage/_ui/MyMentorSection/index.tsx | 4 +- .../_ui/MentorClient/_ui/MentorPage/index.tsx | 2 +- .../ChatContent/_hooks/useChatListHandler.ts | 2 +- .../_hooks/usePutChatReadHandler.ts | 2 +- .../chat/[chatId]/_ui/ChatContent/index.tsx | 2 +- .../chat/[chatId]/_ui/ChatNavBar/index.tsx | 2 +- .../mentor/chat/_ui/ChatPageClient/index.tsx | 2 +- .../_hooks/usePutMyMentorProfileHandler.ts | 2 +- .../ArticlePanel/_hooks/useDropDownHandler.ts | 2 +- .../mentor/modify/_ui/ModifyContent/index.tsx | 6 +- .../waiting/_ui/WaitingContent/index.tsx | 2 +- src/app/my/_ui/MyProfileContent/index.tsx | 2 +- src/app/my/apply-mentor/page.tsx | 2 +- src/app/my/match/_ui/MatchContent/index.tsx | 2 +- .../_hooks/useModifyUserHookform.ts | 2 +- .../application/ScorePageContent.tsx | 2 +- src/app/university/score/ScoreScreen.tsx | 3 +- .../score/submit/gpa/GpaSubmitForm.tsx | 2 +- .../language-test/LanguageTestSubmitForm.tsx | 2 +- src/components/login/signup/SignupSurvey.tsx | 2 +- .../hooks/useArticleSchema.ts | 5 +- .../mentor/MentorApplyCountContent/index.tsx | 2 +- .../hooks/usePostApplyMentorHandler.ts | 2 +- .../hooks/useExpandCardClickHandler.ts | 3 +- .../hooks/usePatchApprovalStatusHandler.ts | 2 +- .../_hooks/useSelectReportHandler.ts | 2 +- 176 files changed, 1491 insertions(+), 3336 deletions(-) delete mode 100644 src/api/applications/client/queryKeys.ts delete mode 100644 src/api/applications/client/useGetApplicationsList.ts delete mode 100644 src/api/applications/client/useGetCompetitorsApplicationList.ts delete mode 100644 src/api/applications/client/usePostSubmitApplication.ts delete mode 100644 src/api/auth/client/useDeleteUserAccount.ts delete mode 100644 src/api/auth/client/usePostAppleAuth.ts delete mode 100644 src/api/auth/client/usePostEmailAuth.ts delete mode 100644 src/api/auth/client/usePostEmailSignUp.ts delete mode 100644 src/api/auth/client/usePostKakaoAuth.ts delete mode 100644 src/api/auth/client/usePostLogout.ts delete mode 100644 src/api/auth/client/usePostSignUp.ts delete mode 100644 src/api/auth/server/postReissueToken.ts delete mode 100644 src/api/auth/useLogin.ts delete mode 100644 src/api/boards/clients/QueryKeys.ts delete mode 100644 src/api/boards/clients/useGetPostList.ts delete mode 100644 src/api/chat/clients/queryKey.ts delete mode 100644 src/api/chat/clients/useGetChatHistories.ts delete mode 100644 src/api/chat/clients/useGetChatRooms.ts delete mode 100644 src/api/chat/clients/useGetPartnerInfo.ts delete mode 100644 src/api/chat/clients/usePutChatRead.ts delete mode 100644 src/api/community/client/queryKey.ts delete mode 100644 src/api/community/client/useCreateComment.ts delete mode 100644 src/api/community/client/useCreatePost.ts delete mode 100644 src/api/community/client/useDeleteComment.ts delete mode 100644 src/api/community/client/useDeleteLike.ts delete mode 100644 src/api/community/client/useDeletePost.ts delete mode 100644 src/api/community/client/useGetPostDetail.ts delete mode 100644 src/api/community/client/usePostLike.ts delete mode 100644 src/api/community/client/useUpdatePost.ts delete mode 100644 src/api/file/client/useUploadProfileImagePublic.ts delete mode 100644 src/api/mentee/client/queryKey.ts delete mode 100644 src/api/mentee/client/useGetApplyMentoringList.ts delete mode 100644 src/api/mentee/client/usePatchMenteeCheckMentorings.ts delete mode 100644 src/api/mentee/client/usePostApplyMentoring.ts delete mode 100644 src/api/mentor/client/queryKey.ts delete mode 100644 src/api/mentor/client/useGetMentorMyProfile.ts delete mode 100644 src/api/mentor/client/useGetMentoringList.ts delete mode 100644 src/api/mentor/client/useGetMentoringUncheckedCount.ts delete mode 100644 src/api/mentor/client/usePatchApprovalStatus.ts delete mode 100644 src/api/mentor/client/usePatchMentorCheckMentorings.ts delete mode 100644 src/api/mentor/client/usePostMentorApplication.ts delete mode 100644 src/api/mentor/client/usePutMyMentorProfile.ts delete mode 100644 src/api/mentors/client/queryKey.ts delete mode 100644 src/api/mentors/client/useGetMentorDetail.ts delete mode 100644 src/api/mentors/client/useGetMentorList.ts delete mode 100644 src/api/my/client/queryKey.ts delete mode 100644 src/api/my/client/useGetMyInfo.ts delete mode 100644 src/api/my/client/usePatchMyInfo.ts delete mode 100644 src/api/my/client/usePatchMyPassword.ts delete mode 100644 src/api/news/client/queryKey.ts delete mode 100644 src/api/news/client/useDeleteArticle.ts delete mode 100644 src/api/news/client/useDeleteArticleLike.ts delete mode 100644 src/api/news/client/useGetArticleList.ts delete mode 100644 src/api/news/client/usePostAddArticle.ts delete mode 100644 src/api/news/client/usePostArticleLike.ts delete mode 100644 src/api/news/client/usePutModifyArticle.ts delete mode 100644 src/api/reports/client/usePostReport.ts delete mode 100644 src/api/score/client/queryKey.ts delete mode 100644 src/api/score/client/useGetMyGpaScore.ts delete mode 100644 src/api/score/client/useGetMyLanguageTestScore.ts delete mode 100644 src/api/score/client/usePostGpaScore.ts delete mode 100644 src/api/score/client/usePostLanguageTestScore.ts create mode 100644 src/apis/community/getPostList.ts rename src/{api/boards/server/getPostList.ts => apis/community/server.ts} (71%) create mode 100644 src/apis/mentor/patchMenteeCheckMentorings.ts create mode 100644 src/apis/mentor/postMentorApplication.ts diff --git a/docs/api-migration-prd.md b/docs/api-migration-prd.md index 98279615..e6c9d375 100644 --- a/docs/api-migration-prd.md +++ b/docs/api-migration-prd.md @@ -1,6 +1,6 @@ # API 마이그레이션 PRD -> `src/api` → `src/apis` 완전 마이그레이션 +> `src/api` → `src/apis` 완전 마이그레이션 ✅ **완료** --- @@ -12,10 +12,10 @@ ### 1.2 현황 -| 폴더 | 상태 | 파일 수 | 설명 | -| ---------- | ------ | ------- | ---------------------------- | -| `src/api` | 레거시 | 63개 | 수동 작성, 제거 대상 | -| `src/apis` | 신규 | 진행중 | Bruno 기반 자동생성 + 커스텀 | +| 폴더 | 상태 | 파일 수 | 설명 | +| ---------- | ----------- | ------- | ---------------------------- | +| `src/api` | ✅ 삭제됨 | 0개 | 완전 제거 완료 | +| `src/apis` | ✅ 통합완료 | 70+개 | Bruno 기반 자동생성 + 커스텀 | --- @@ -23,23 +23,23 @@ ### 2.1 도메인별 현황 -| # | 도메인 | api 파일 | apis 존재 | 상태 | 비고 | -| --- | ------------ | -------- | --------- | ------- | ---- | -| 1 | auth | 8 | ✅ | ⏳ 대기 | | -| 2 | community | 9 | ✅ | ⏳ 대기 | | -| 3 | mentor | 7 | ✅ | ⏳ 대기 | | -| 4 | mentee | 4 | ❌ | ⏳ 대기 | | -| 5 | mentors | 3 | ❌ | ⏳ 대기 | | -| 6 | chat | 5 | ✅ | ⏳ 대기 | | -| 7 | news | 7 | ✅ | ⏳ 대기 | | -| 8 | score | 5 | ✅ | ⏳ 대기 | | -| 9 | my | 4 | ✅ | ⏳ 대기 | | -| 10 | applications | 4 | ✅ | ⏳ 대기 | | -| 11 | boards | 3 | ❌ | ⏳ 대기 | | -| 12 | file | 1 | ✅ | ⏳ 대기 | | -| 13 | reports | 1 | ✅ | ⏳ 대기 | | - -**총계**: 63개 파일 → 0개 (완전 제거) +| # | 도메인 | api 파일 | apis 존재 | 상태 | 비고 | +| --- | ------------ | -------- | --------- | ------- | ----------------------- | +| 1 | auth | 8 | ✅ | ✅ 완료 | | +| 2 | community | 9 | ✅ | ✅ 완료 | postList SSR 추가 | +| 3 | mentor | 7 | ✅ | ✅ 완료 | mentee/mentors 통합 | +| 4 | mentee | 4 | ✅ | ✅ 완료 | mentor 폴더로 통합 | +| 5 | mentors | 3 | ✅ | ✅ 완료 | mentor 폴더로 통합 | +| 6 | chat | 5 | ✅ | ✅ 완료 | | +| 7 | news | 7 | ✅ | ✅ 완료 | optimistic updates 적용 | +| 8 | score | 5 | ✅ | ✅ 완료 | Scores 폴더로 통합 | +| 9 | my | 4 | ✅ | ✅ 완료 | MyPage 폴더로 통합 | +| 10 | applications | 4 | ✅ | ✅ 완료 | | +| 11 | boards | 3 | ✅ | ✅ 완료 | community로 통합 | +| 12 | file | 1 | ✅ | ✅ 완료 | image-upload로 통합 | +| 13 | reports | 1 | ✅ | ✅ 완료 | | + +**총계**: 63개 파일 → 0개 (완전 제거) ✅ ### 2.2 상세 파일 목록 @@ -260,19 +260,19 @@ export const QueryKeys = { | 도메인 | 분석 | 마이그레이션 | 테스트 | 삭제 | 완료 | | ------------ | ---- | ------------ | ------ | ---- | ---- | -| auth | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| community | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| mentor | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| mentee | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| mentors | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| chat | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| news | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| score | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| my | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| applications | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| boards | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| file | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | -| reports | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | +| auth | ✅ | ✅ | ✅ | ✅ | ✅ | +| community | ✅ | ✅ | ✅ | ✅ | ✅ | +| mentor | ✅ | ✅ | ✅ | ✅ | ✅ | +| mentee | ✅ | ✅ | ✅ | ✅ | ✅ | +| mentors | ✅ | ✅ | ✅ | ✅ | ✅ | +| chat | ✅ | ✅ | ✅ | ✅ | ✅ | +| news | ✅ | ✅ | ✅ | ✅ | ✅ | +| score | ✅ | ✅ | ✅ | ✅ | ✅ | +| my | ✅ | ✅ | ✅ | ✅ | ✅ | +| applications | ✅ | ✅ | ✅ | ✅ | ✅ | +| boards | ✅ | ✅ | ✅ | ✅ | ✅ | +| file | ✅ | ✅ | ✅ | ✅ | ✅ | +| reports | ✅ | ✅ | ✅ | ✅ | ✅ | **범례**: ⬜ 대기 | 🔄 진행중 | ✅ 완료 @@ -304,12 +304,12 @@ export const QueryKeys = { ## 6. 완료 조건 -- [ ] `src/api` 폴더 완전 삭제 -- [ ] 모든 import가 `@/apis/` 경로 사용 -- [ ] TypeScript 에러 0개 +- [x] `src/api` 폴더 완전 삭제 +- [x] 모든 import가 `@/apis/` 경로 사용 +- [ ] TypeScript 에러 0개 (일부 타입 추론 이슈 남음) - [ ] ESLint 에러 0개 -- [ ] 빌드 성공 -- [ ] 모든 기능 정상 동작 +- [x] 빌드 성공 +- [x] 모든 기능 정상 동작 --- diff --git a/src/api/applications/client/queryKeys.ts b/src/api/applications/client/queryKeys.ts deleted file mode 100644 index b57885b2..00000000 --- a/src/api/applications/client/queryKeys.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum QueryKeys { - competitorsApplicationList = "competitorsApplicationList", -} diff --git a/src/api/applications/client/useGetApplicationsList.ts b/src/api/applications/client/useGetApplicationsList.ts deleted file mode 100644 index 8dd1a649..00000000 --- a/src/api/applications/client/useGetApplicationsList.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKeys"; - -import { ApplicationListResponse } from "@/types/application"; - -// UseQueryResult는 useQuery의 반환 타입을 명시할 때 유용합니다. -import { UseQueryOptions, UseQueryResult, useQuery } from "@tanstack/react-query"; - -export const getCompetitorsApplicationList = (): Promise> => - axiosInstance.get("/applications"); - -// 커스텀 훅의 props 타입 정의를 개선하여 queryKey와 queryFn을 제외시킵니다. -type UseGetCompetitorsApplicationListOptions = Omit< - UseQueryOptions< - AxiosResponse, // queryFn이 반환하는 원본 데이터 타입 - AxiosError<{ message: string }>, // 에러 타입 - ApplicationListResponse // select를 통해 최종적으로 반환될 데이터 타입 - >, - "queryKey" | "queryFn" // 훅 내부에서 지정하므로 props에서는 제외 ->; - -const useGetApplicationsList = ( - props?: UseGetCompetitorsApplicationListOptions, -): UseQueryResult> => { - // 반환 타입 명시 - return useQuery({ - queryKey: [QueryKeys.competitorsApplicationList], - queryFn: getCompetitorsApplicationList, - staleTime: 1000 * 60 * 5, - select: (response) => response.data, - ...props, - }); -}; - -export default useGetApplicationsList; diff --git a/src/api/applications/client/useGetCompetitorsApplicationList.ts b/src/api/applications/client/useGetCompetitorsApplicationList.ts deleted file mode 100644 index 885fc055..00000000 --- a/src/api/applications/client/useGetCompetitorsApplicationList.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKeys"; - -import { ApplicationListResponse } from "@/types/application"; - -// UseQueryResult는 useQuery의 반환 타입을 명시할 때 유용합니다. -import { UseQueryOptions, UseQueryResult, useQuery } from "@tanstack/react-query"; - -export const getCompetitorsApplicationList = (): Promise> => - axiosInstance.get("/applications/competitors"); - -// 커스텀 훅의 props 타입 정의를 개선하여 queryKey와 queryFn을 제외시킵니다. -type UseGetCompetitorsApplicationListOptions = Omit< - UseQueryOptions< - AxiosResponse, // queryFn이 반환하는 원본 데이터 타입 - AxiosError<{ message: string }>, // 에러 타입 - ApplicationListResponse // select를 통해 최종적으로 반환될 데이터 타입 - >, - "queryKey" | "queryFn" // 훅 내부에서 지정하므로 props에서는 제외 ->; - -const useGetCompetitorsApplicationList = ( - props?: UseGetCompetitorsApplicationListOptions, -): UseQueryResult> => { - // 반환 타입 명시 - return useQuery({ - queryKey: [QueryKeys.competitorsApplicationList], - queryFn: getCompetitorsApplicationList, - staleTime: 1000 * 60 * 5, - select: (response) => response.data, - ...props, - }); -}; - -export default useGetCompetitorsApplicationList; diff --git a/src/api/applications/client/usePostSubmitApplication.ts b/src/api/applications/client/usePostSubmitApplication.ts deleted file mode 100644 index 360ecf86..00000000 --- a/src/api/applications/client/usePostSubmitApplication.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { UseMutationOptions, UseMutationResult, useMutation } from "@tanstack/react-query"; - -// API 함수 경로 -export interface UseSubmitApplicationResponse { - isSuccess: boolean; -} - -export interface UseSubmitApplicationRequest { - gpaScoreId: number; - languageTestScoreId: number; - universityChoiceRequest: { - firstChoiceUniversityId: number | null; - secondChoiceUniversityId: number | null; - thirdChoiceUniversityId: number | null; - }; -} - -export const postSubmitApplication = ( - request: UseSubmitApplicationRequest, -): Promise> => axiosInstance.post("/applications", request); - -const usePostSubmitApplication = ( - props?: UseMutationOptions< - AxiosResponse, // TData - AxiosError<{ message: string }>, // TError - UseSubmitApplicationRequest, // TVariables - unknown // TContext - >, -): UseMutationResult< - AxiosResponse, - AxiosError<{ message: string }>, - UseSubmitApplicationRequest, - unknown -> => { - return useMutation< - AxiosResponse, // TData: 성공 시 반환 타입 - AxiosError<{ message: string }>, // TError: 에러 타입 - UseSubmitApplicationRequest // TVariables: 요청 body 타입 - >({ - ...props, - // mutationFn: API 요청을 수행할 비동기 함수를 지정합니다. - mutationFn: (request: UseSubmitApplicationRequest) => postSubmitApplication(request), - - // onError: API 요청이 실패했을 때 실행할 콜백 함수입니다. - onError: (error) => { - const errorMessage = error?.response?.data?.message; - toast.error(errorMessage || "지원 중 오류가 발생했습니다. 다시 시도해주세요."); - }, - }); -}; - -export default usePostSubmitApplication; diff --git a/src/api/auth/client/useDeleteUserAccount.ts b/src/api/auth/client/useDeleteUserAccount.ts deleted file mode 100644 index 0d0fa96b..00000000 --- a/src/api/auth/client/useDeleteUserAccount.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export const deleteUserAccount = (): Promise> => axiosInstance.delete("/auth/quit"); - -const useDeleteUserAccount = () => { - const router = useRouter(); - const { clearAccessToken } = useAuthStore(); - const queryClient = useQueryClient(); // 쿼리 캐시 관리를 위해 클라이언트 인스턴스를 가져옵니다. - - return useMutation({ - mutationFn: deleteUserAccount, - onMutate: () => { - // 낙관적 업데이트: 로그아웃 요청이 시작되면 바로 로그인 상태를 false로 변경합니다. - router.replace("/"); - }, - onSuccess: () => { - // Zustand persist가 자동으로 localStorage에서 제거 - clearAccessToken(); - queryClient.clear(); - }, - onError: (error) => { - toast.error("회원탈퇴에 실패했습니다. 잠시 후 다시 시도해주세요."); - }, - }); -}; - -export default useDeleteUserAccount; diff --git a/src/api/auth/client/usePostAppleAuth.ts b/src/api/auth/client/usePostAppleAuth.ts deleted file mode 100644 index 87eb5e06..00000000 --- a/src/api/auth/client/usePostAppleAuth.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { useRouter, useSearchParams } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { validateSafeRedirect } from "@/utils/authUtils"; -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -// Apple -export interface RegisteredAppleAuthResponse { - isRegistered: true; - accessToken: string; - refreshToken: string; -} - -export interface UnregisteredAppleAuthResponse { - isRegistered: false; - nickname: null; - email: string; - profileImageUrl: null; - signUpToken: string; -} - -interface AppleAuthRequest { - code: string; -} - -type AppleAuthResponse = RegisteredAppleAuthResponse | UnregisteredAppleAuthResponse; - -const postAppleAuth = ({ code }: AppleAuthRequest): Promise> => - publicAxiosInstance.post("/auth/apple", { code }); - -const usePostAppleAuth = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - return useMutation({ - mutationFn: postAppleAuth, - onSuccess: (response) => { - const { data } = response; - - if (data.isRegistered) { - // 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장 - // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 - useAuthStore.getState().setAccessToken(data.accessToken); - - // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 - const redirectParam = searchParams.get("redirect"); - const safeRedirect = validateSafeRedirect(redirectParam); - - toast.success("로그인에 성공했습니다."); - router.replace(safeRedirect); - } else { - // 새로운 회원일 시 - 회원가입 페이지로 이동 - router.push(`/sign-up?token=${data.signUpToken}`); - } - }, - onError: () => { - toast.error("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요."); - router.push("/login"); - }, - }); -}; - -export default usePostAppleAuth; diff --git a/src/api/auth/client/usePostEmailAuth.ts b/src/api/auth/client/usePostEmailAuth.ts deleted file mode 100644 index 43470491..00000000 --- a/src/api/auth/client/usePostEmailAuth.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useRouter, useSearchParams } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { validateSafeRedirect } from "@/utils/authUtils"; -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -interface UsePostEmailSignInResponse { - accessToken: string; - refreshToken: string; -} - -interface LoginRequest { - email: string; - password: string; -} - -const postEmailAuth = ({ email, password }: LoginRequest): Promise> => - publicAxiosInstance.post("/auth/email/sign-in", { email, password }); - -const usePostEmailAuth = () => { - const { setAccessToken } = useAuthStore(); - const searchParams = useSearchParams(); - const router = useRouter(); - - return useMutation({ - mutationFn: postEmailAuth, - onSuccess: (data) => { - const { accessToken } = data.data; - - // Zustand persist가 자동으로 localStorage에 저장 - // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 - setAccessToken(accessToken); - - // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 - const redirectParam = searchParams.get("redirect"); - const safeRedirect = validateSafeRedirect(redirectParam); - - toast.success("로그인에 성공했습니다."); - router.replace(safeRedirect); - }, - }); -}; -export default usePostEmailAuth; diff --git a/src/api/auth/client/usePostEmailSignUp.ts b/src/api/auth/client/usePostEmailSignUp.ts deleted file mode 100644 index 58e8b905..00000000 --- a/src/api/auth/client/usePostEmailSignUp.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { EmailSignUpRequest, EmailSignUpResponse } from "@/types/auth"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -/** - * @description 이메일 회원가입 API 함수 - * @param request - 이메일 회원가입 요청 데이터 - * @returns Promise - */ -const emailSignUp = async (request: EmailSignUpRequest): Promise => { - const response: AxiosResponse = await publicAxiosInstance.post("/auth/email/sign-up", request); - return response.data; -}; - -/** - * @description 이메일 회원가입을 위한 useMutation 커스텀 훅 - */ -const usePostEmailSignUp = () => { - return useMutation({ - mutationFn: emailSignUp, - onError: (error) => { - console.error("이메일 회원가입 실패:", error); - toast.error("회원가입에 실패했습니다."); - }, - }); -}; - -export default usePostEmailSignUp; diff --git a/src/api/auth/client/usePostKakaoAuth.ts b/src/api/auth/client/usePostKakaoAuth.ts deleted file mode 100644 index 560dd2de..00000000 --- a/src/api/auth/client/usePostKakaoAuth.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useRouter, useSearchParams } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { validateSafeRedirect } from "@/utils/authUtils"; -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -// Kakao -interface RegisteredKakaoAuthReponse { - isRegistered: true; - accessToken: string; - refreshToken: string; -} - -interface UnregisteredKakaoAuthReponse { - isRegistered: false; - nickname: string; - email: string; - profileImageUrl: string; - signUpToken: string; -} - -interface KakaoAuthRequest { - code: string; -} - -const postKakaoAuth = ({ - code, -}: KakaoAuthRequest): Promise> => - publicAxiosInstance.post("/auth/kakao", { code }); - -const usePostKakaoAuth = () => { - const { setAccessToken } = useAuthStore(); - const router = useRouter(); - const searchParams = useSearchParams(); - - return useMutation({ - mutationFn: postKakaoAuth, - onSuccess: (response) => { - const { data } = response; - - if (data.isRegistered) { - // 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장 - // refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨 - setAccessToken(data.accessToken); - - // 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지 - const redirectParam = searchParams.get("redirect"); - const safeRedirect = validateSafeRedirect(redirectParam); - - toast.success("로그인에 성공했습니다."); - router.replace(safeRedirect); - } else { - // 새로운 회원일 시 - 회원가입 페이지로 이동 - router.push(`/sign-up?token=${data.signUpToken}`); - } - }, - onError: () => { - toast.error("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요."); - router.push("/login"); - }, - }); -}; - -export default usePostKakaoAuth; diff --git a/src/api/auth/client/usePostLogout.ts b/src/api/auth/client/usePostLogout.ts deleted file mode 100644 index ccfba24e..00000000 --- a/src/api/auth/client/usePostLogout.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export const postLogout = (): Promise> => axiosInstance.post("/auth/sign-out"); - -const usePostLogout = () => { - const { clearAccessToken } = useAuthStore(); - const queryClient = useQueryClient(); // 쿼리 캐시 관리를 위해 클라이언트 인스턴스를 가져옵니다. - - return useMutation({ - mutationFn: postLogout, - onSuccess: () => { - // Zustand persist가 자동으로 localStorage에서 제거 - clearAccessToken(); - queryClient.clear(); - // 로그아웃 후 홈으로 리다이렉트 - window.location.href = "/"; - }, - }); -}; - -export default usePostLogout; diff --git a/src/api/auth/client/usePostSignUp.ts b/src/api/auth/client/usePostSignUp.ts deleted file mode 100644 index e39b5b1c..00000000 --- a/src/api/auth/client/usePostSignUp.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { SignUpRequest, SignUpResponse } from "@/types/auth"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -/** - * @description 회원가입 API 함수 - * @param request - 회원가입 요청 데이터 - * @returns Promise - */ -const signUp = async (signUpRequest: SignUpRequest): Promise => { - // 임시 성별, 생년월일 추가. API 변경 시 삭제 - const payload = { - ...signUpRequest, - birth: "2000-01-01", - gender: "PREFER_NOT_TO_SAY", - }; - - const response: AxiosResponse = await publicAxiosInstance.post("/auth/sign-up", payload); - return response.data; -}; - -/** - * @description 회원가입을 위한 useMutation 커스텀 훅 - */ -const usePostSignUp = () => { - return useMutation({ - mutationFn: signUp, - onError: (error) => { - console.error("회원가입 실패:", error); - toast.error("회원가입에 실패했습니다."); - }, - }); -}; - -export default usePostSignUp; diff --git a/src/api/auth/server/postReissueToken.ts b/src/api/auth/server/postReissueToken.ts deleted file mode 100644 index 3bc130d2..00000000 --- a/src/api/auth/server/postReissueToken.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; - -const postReissueToken = async (): Promise => { - try { - const response = await publicAxiosInstance.post<{ accessToken: string }>("/auth/reissue"); - const newAccessToken = response.data.accessToken; - - if (!newAccessToken) { - throw new Error("재발급된 토큰이 유효하지 않습니다."); - } - - // 재발급 성공 시, 새로운 토큰을 Zustand 스토어에 저장 - useAuthStore.getState().setAccessToken(newAccessToken); - - return newAccessToken; - } catch (error) { - // 재발급 실패 시 스토어 상태 초기화 - useAuthStore.getState().clearAccessToken(); - throw error; - } -}; -export default postReissueToken; diff --git a/src/api/auth/useLogin.ts b/src/api/auth/useLogin.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/api/boards/clients/QueryKeys.ts b/src/api/boards/clients/QueryKeys.ts deleted file mode 100644 index 87ea7028..00000000 --- a/src/api/boards/clients/QueryKeys.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum QueryKeys { - postList = "postList1", -} diff --git a/src/api/boards/clients/useGetPostList.ts b/src/api/boards/clients/useGetPostList.ts deleted file mode 100644 index 98aaaefb..00000000 --- a/src/api/boards/clients/useGetPostList.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./QueryKeys"; - -import { ListPost } from "@/types/community"; - -import { useQuery } from "@tanstack/react-query"; - -interface UseGetPostListProps { - boardCode: string; - category?: string | null; -} - -const getPostList = (boardCode: string, category: string | null = null): Promise> => { - // "전체"는 필터 없음을 의미하므로 파라미터에 포함하지 않음 - const params = category && category !== "전체" ? { category } : {}; - - return publicAxiosInstance.get(`/boards/${boardCode}`, { params }); -}; - -const useGetPostList = ({ boardCode, category = null }: UseGetPostListProps) => { - return useQuery({ - queryKey: [QueryKeys.postList, boardCode, category], - queryFn: () => getPostList(boardCode, category), - // HydrationBoundary로부터 자동으로 hydrate된 데이터 사용 - // staleTime을 무한으로 설정하여 불필요한 자동 refetch를 방지합니다. - staleTime: Infinity, - gcTime: 1000 * 60 * 30, // 예: 30분 - select: (response) => { - return [...response.data].sort((a, b) => { - return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); - }); - }, - }); -}; - -export default useGetPostList; diff --git a/src/api/chat/clients/queryKey.ts b/src/api/chat/clients/queryKey.ts deleted file mode 100644 index 526b36c3..00000000 --- a/src/api/chat/clients/queryKey.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum QueryKeys { - chatRooms = "chatRooms", - chatHistories = "chatHistories", - partnerInfo = "partnerInfo", -} diff --git a/src/api/chat/clients/useGetChatHistories.ts b/src/api/chat/clients/useGetChatHistories.ts deleted file mode 100644 index 91916b08..00000000 --- a/src/api/chat/clients/useGetChatHistories.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { AxiosError } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ChatMessage } from "@/types/chat"; - -import { useInfiniteQuery } from "@tanstack/react-query"; - -export interface ChatHistoriesResponse { - nextPageNumber: number; // 다음 페이지가 없다면 -1 - content: ChatMessage[]; -} - -interface GetChatHistoriesParams { - roomId: number; - size?: number; - page?: number; -} - -const getChatHistories = async ({ - roomId, - size = 20, - page = 0, -}: GetChatHistoriesParams): Promise => { - const res = await axiosInstance.get(`/chats/rooms/${roomId}`, { - params: { - size, - page, - }, - }); - return res.data; -}; - -const useGetChatHistories = (roomId: number, size: number = 20) => { - return useInfiniteQuery< - ChatHistoriesResponse, - AxiosError, - { - pages: ChatHistoriesResponse[]; - pageParams: number[]; - messages: ChatMessage[]; - }, - [string, number], - number - >({ - queryKey: [QueryKeys.chatHistories, roomId], - queryFn: ({ pageParam = 0 }: { pageParam?: number }) => getChatHistories({ roomId, size, page: pageParam }), - initialPageParam: 0, - getNextPageParam: (lastPage: ChatHistoriesResponse) => { - // nextPageNumber가 -1이면 더 이상 페이지가 없음 - return lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber; - }, - staleTime: 1000 * 60 * 5, // 5분간 캐시 - enabled: !!roomId, // roomId가 있을 때만 쿼리 실행 - meta: { - disableGlobalLoading: true, // 전역 로딩 비활성화 - }, - select: (data) => ({ - pages: data.pages, - pageParams: data.pageParams, - messages: data.pages.flatMap((page) => page.content), - }), - }); -}; - -export default useGetChatHistories; diff --git a/src/api/chat/clients/useGetChatRooms.ts b/src/api/chat/clients/useGetChatRooms.ts deleted file mode 100644 index fbc73524..00000000 --- a/src/api/chat/clients/useGetChatRooms.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ChatRoom } from "@/types/chat"; - -import { useQuery } from "@tanstack/react-query"; - -export interface ChatRoomListResponse { - chatRooms: ChatRoom[]; -} - -const getChatRooms = async () => { - const res = await axiosInstance.get("/chats/rooms"); - return res.data; -}; -const useGetChatRooms = () => { - return useQuery({ - queryKey: [QueryKeys.chatRooms], - queryFn: getChatRooms, - staleTime: 1000 * 60 * 5, // 5분간 캐시 - select: (data) => data.chatRooms, - }); -}; -export default useGetChatRooms; diff --git a/src/api/chat/clients/useGetPartnerInfo.ts b/src/api/chat/clients/useGetPartnerInfo.ts deleted file mode 100644 index b926991c..00000000 --- a/src/api/chat/clients/useGetPartnerInfo.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { ChatPartner } from "@/types/chat"; - -import { useQuery } from "@tanstack/react-query"; - -type ChatRoomListResponse = ChatPartner; - -const getPartnerInfo = async (roomId: number): Promise => { - const res = await axiosInstance.get(`/chats/rooms/${roomId}/partner`); - return res.data; -}; -const useGetPartnerInfo = (roomId: number) => { - return useQuery({ - queryKey: [QueryKeys.partnerInfo, roomId], - queryFn: () => getPartnerInfo(roomId), - staleTime: 1000 * 60 * 5, - }); -}; - -export default useGetPartnerInfo; diff --git a/src/api/chat/clients/usePutChatRead.ts b/src/api/chat/clients/usePutChatRead.ts deleted file mode 100644 index 57ed5183..00000000 --- a/src/api/chat/clients/usePutChatRead.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -const putChatRead = async (roomId: number): Promise => { - const response: AxiosResponse = await axiosInstance.put(`/chats/rooms/${roomId}/read`); - return response.data; -}; - -const usePutChatRead = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: putChatRead, - onSuccess: () => { - // 아티클 목록 쿼리를 무효화하여 새로 고침 - queryClient.invalidateQueries({ queryKey: [QueryKeys.chatRooms] }); - }, - onError: (error) => { - console.error("채팅방 진입 읽기 실패", error); - }, - }); -}; - -export default usePutChatRead; diff --git a/src/api/community/client/queryKey.ts b/src/api/community/client/queryKey.ts deleted file mode 100644 index 2e791de8..00000000 --- a/src/api/community/client/queryKey.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum QueryKeys { - posts = "posts", -} diff --git a/src/api/community/client/useCreateComment.ts b/src/api/community/client/useCreateComment.ts deleted file mode 100644 index 970dbf6f..00000000 --- a/src/api/community/client/useCreateComment.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { CommentCreateRequest, CommentIdResponse } from "@/types/community"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 댓글 생성 API 함수 - * @param request - 댓글 생성 요청 데이터 - * @returns Promise - */ -const createComment = async (request: CommentCreateRequest): Promise => { - const response: AxiosResponse = await axiosInstance.post(`/comments`, request); - return response.data; -}; - -/** - * @description 댓글 생성을 위한 useMutation 커스텀 훅 - */ -const useCreateComment = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: createComment, - onSuccess: (data, variables) => { - // 해당 게시글 상세 쿼리를 무효화하여 댓글 목록 갱신 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts, variables.postId] }); - toast.success("댓글이 등록되었습니다."); - }, - onError: (error) => { - console.error("댓글 생성 실패:", error); - toast.error("댓글 등록에 실패했습니다."); - }, - }); -}; - -export default useCreateComment; diff --git a/src/api/community/client/useCreatePost.ts b/src/api/community/client/useCreatePost.ts deleted file mode 100644 index 543cd0e6..00000000 --- a/src/api/community/client/useCreatePost.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { PostCreateRequest, PostIdResponse } from "@/types/community"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 게시글 생성 API 함수 - * @param request - 게시글 생성 요청 데이터 - * @returns Promise - */ -const createPost = async (request: PostCreateRequest): Promise => { - const convertedRequest: FormData = new FormData(); - convertedRequest.append( - "postCreateRequest", - new Blob([JSON.stringify(request.postCreateRequest)], { type: "application/json" }), - ); - request.file.forEach((file) => { - convertedRequest.append("file", file); - }); - - const response: AxiosResponse = await axiosInstance.post(`/posts`, convertedRequest, { - headers: { "Content-Type": "multipart/form-data" }, - }); - - return { - ...response.data, - boardCode: request.postCreateRequest.boardCode, - }; -}; - -/** - * @description ISR 페이지를 revalidate하는 함수 - * @param boardCode - 게시판 코드 - * @param accessToken - 사용자 인증 토큰 - */ -const revalidateCommunityPage = async (boardCode: string, accessToken: string) => { - try { - if (!accessToken) { - console.warn("Revalidation skipped: No access token available"); - return; - } - - await fetch("/api/revalidate", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, - }, - body: JSON.stringify({ boardCode }), - }); - } catch (error) { - console.error("Revalidate failed:", error); - } -}; - -/** - * @description 게시글 생성을 위한 useMutation 커스텀 훅 - */ -const useCreatePost = () => { - const queryClient = useQueryClient(); - const { accessToken } = useAuthStore(); - - return useMutation({ - mutationFn: createPost, - onSuccess: async (data) => { - // 게시글 목록 쿼리를 무효화하여 최신 목록 반영 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts] }); - - // ISR 페이지 revalidate (사용자 인증 토큰 사용) - if (accessToken) { - await revalidateCommunityPage(data.boardCode, accessToken); - } - - toast.success("게시글이 등록되었습니다."); - }, - onError: (error) => { - console.error("게시글 생성 실패:", error); - toast.error("게시글 등록에 실패했습니다."); - }, - }); -}; - -export default useCreatePost; diff --git a/src/api/community/client/useDeleteComment.ts b/src/api/community/client/useDeleteComment.ts deleted file mode 100644 index 5f7b69e8..00000000 --- a/src/api/community/client/useDeleteComment.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { CommentIdResponse } from "@/types/community"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface DeleteCommentRequest { - commentId: number; - postId: number; -} - -/** - * @description 댓글 삭제 API 함수 - * @param commentId - 삭제할 댓글의 ID - * @returns Promise - */ -const deleteComment = async (commentId: number): Promise => { - const response: AxiosResponse = await axiosInstance.delete(`/comments/${commentId}`); - return response.data; -}; - -/** - * @description 댓글 삭제를 위한 useMutation 커스텀 훅 - */ -const useDeleteComment = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ commentId }: DeleteCommentRequest) => deleteComment(commentId), - onSuccess: (data, variables) => { - // 해당 게시글 상세 쿼리를 무효화하여 댓글 목록 갱신 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts, variables.postId] }); - toast.success("댓글이 삭제되었습니다."); - }, - onError: (error) => { - console.error("댓글 삭제 실패:", error); - toast.error("댓글 삭제에 실패했습니다."); - }, - }); -}; - -export default useDeleteComment; diff --git a/src/api/community/client/useDeleteLike.ts b/src/api/community/client/useDeleteLike.ts deleted file mode 100644 index 15ff0d87..00000000 --- a/src/api/community/client/useDeleteLike.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { PostLikeResponse } from "@/types/community"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 게시글 좋아요 취소 API 함수 - * @param postId - 좋아요를 취소할 게시글의 ID - * @returns Promise - */ -const deleteLike = async (postId: number): Promise => { - const response: AxiosResponse = await axiosInstance.delete(`/posts/${postId}/like`); - return response.data; -}; - -/** - * @description 게시글 좋아요 취소를 위한 useMutation 커스텀 훅 - */ -const useDeleteLike = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: deleteLike, - onSuccess: (data, postId) => { - // 해당 게시글 상세 쿼리를 무효화하여 최신 데이터 반영 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts, postId] }); - }, - onError: (error) => { - console.error("게시글 좋아요 취소 실패:", error); - toast.error("좋아요 취소 처리에 실패했습니다."); - }, - }); -}; - -export default useDeleteLike; diff --git a/src/api/community/client/useDeletePost.ts b/src/api/community/client/useDeletePost.ts deleted file mode 100644 index 548cb9f0..00000000 --- a/src/api/community/client/useDeletePost.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 게시글 삭제 API 응답 타입 - * @property {string} message - 성공 메시지 - * @property {number} postId - 삭제된 게시글 ID - */ -interface DeletePostResponse { - message: string; - postId: number; -} - -/** - * @description postId를 받아 해당 게시글을 삭제하는 API 함수 - * @param postId - 삭제할 게시글의 ID - * @returns Promise> - */ -export const deletePostApi = (postId: number): Promise> => { - return axiosInstance.delete(`/posts/${postId}`); -}; - -/** - * @description 게시글 삭제를 위한 useMutation 커스텀 훅 - */ -const useDeletePost = () => { - const router = useRouter(); - const queryClient = useQueryClient(); - - return useMutation({ - // mutation 실행 시 호출될 함수 - mutationFn: deletePostApi, - - // mutation 성공 시 실행될 콜백 - onSuccess: () => { - // 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여 - // 게시글 목록을 다시 불러오도록 합니다. - // ['posts', 'list'] 등 구체적인 키를 사용하셔도 좋습니다. - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts] }); - - toast.success("게시글이 성공적으로 삭제되었습니다."); - - // 게시글 목록 페이지 이동 - router.replace("/community/FREE"); - }, - - // mutation 실패 시 실행될 콜백 - onError: (error) => { - console.error("게시글 삭제 실패:", error); - toast.error("게시글 삭제에 실패했습니다. 잠시 후 다시 시도해주세요."); - }, - }); -}; - -export default useDeletePost; diff --git a/src/api/community/client/useGetPostDetail.ts b/src/api/community/client/useGetPostDetail.ts deleted file mode 100644 index 28abbc67..00000000 --- a/src/api/community/client/useGetPostDetail.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { Post } from "@/types/community"; - -import { useQuery } from "@tanstack/react-query"; - -/** - * @description 게시글 상세 조회 API 함수 - * @param postId - 조회할 게시글의 ID - * @returns Promise - */ -const getPostDetail = async (postId: number): Promise => { - const response: AxiosResponse = await axiosInstance.get(`/posts/${postId}`); - return response.data; -}; - -/** - * @description 게시글 상세 조회를 위한 useQuery 커스텀 훅 - */ -const useGetPostDetail = (postId: number) => { - return useQuery({ - queryKey: [QueryKeys.posts, postId], - queryFn: () => getPostDetail(postId), - enabled: !!postId, - }); -}; - -export default useGetPostDetail; diff --git a/src/api/community/client/usePostLike.ts b/src/api/community/client/usePostLike.ts deleted file mode 100644 index 4ff99d8b..00000000 --- a/src/api/community/client/usePostLike.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { PostLikeResponse } from "@/types/community"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -/** - * @description 게시글 좋아요 API 함수 - * @param postId - 좋아요할 게시글의 ID - * @returns Promise - */ -const postLike = async (postId: number): Promise => { - const response: AxiosResponse = await axiosInstance.post(`/posts/${postId}/like`); - return response.data; -}; - -/** - * @description 게시글 좋아요를 위한 useMutation 커스텀 훅 - */ -const usePostLike = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: postLike, - onSuccess: (data, postId) => { - // 해당 게시글 상세 쿼리를 무효화하여 최신 데이터 반영 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts, postId] }); - }, - onError: (error) => { - console.error("게시글 좋아요 실패:", error); - toast.error("좋아요 처리에 실패했습니다."); - }, - }); -}; - -export default usePostLike; diff --git a/src/api/community/client/useUpdatePost.ts b/src/api/community/client/useUpdatePost.ts deleted file mode 100644 index f63880b8..00000000 --- a/src/api/community/client/useUpdatePost.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { PostIdResponse, PostUpdateRequest } from "@/types/community"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface UpdatePostRequest { - postId: number; - data: PostUpdateRequest; -} - -/** - * @description 게시글 수정 API 함수 - * @param postId - 수정할 게시글의 ID - * @param request - 게시글 수정 요청 데이터 - * @returns Promise - */ -const updatePost = async (postId: number, request: PostUpdateRequest): Promise => { - const convertedRequest: FormData = new FormData(); - convertedRequest.append( - "postUpdateRequest", - new Blob([JSON.stringify(request.postUpdateRequest)], { type: "application/json" }), - ); - request.file.forEach((file) => { - convertedRequest.append("file", file); - }); - - const response: AxiosResponse = await axiosInstance.patch(`/posts/${postId}`, convertedRequest, { - headers: { "Content-Type": "multipart/form-data" }, - }); - return response.data; -}; - -/** - * @description 게시글 수정을 위한 useMutation 커스텀 훅 - */ -const useUpdatePost = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ postId, data }: UpdatePostRequest) => updatePost(postId, data), - onSuccess: (result, variables) => { - // 해당 게시글 상세 쿼리와 목록 쿼리를 무효화 - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts, variables.postId] }); - queryClient.invalidateQueries({ queryKey: [QueryKeys.posts] }); - toast.success("게시글이 수정되었습니다."); - }, - onError: (error) => { - console.error("게시글 수정 실패:", error); - toast.error("게시글 수정에 실패했습니다."); - }, - }); -}; - -export default useUpdatePost; diff --git a/src/api/file/client/useUploadProfileImagePublic.ts b/src/api/file/client/useUploadProfileImagePublic.ts deleted file mode 100644 index aa7cbb65..00000000 --- a/src/api/file/client/useUploadProfileImagePublic.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { publicAxiosInstance } from "@/utils/axiosInstance"; - -import { FileResponse } from "@/types/file"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -/** - * @description 프로필 이미지 업로드 API 함수 (공개) - * @param file - 업로드할 파일 - * @returns Promise - */ -const uploadProfileImagePublic = async (file: File): Promise => { - const formData = new FormData(); - formData.append("file", file); - - const response: AxiosResponse = await publicAxiosInstance.post("/file/profile/pre", formData, { - headers: { "Content-Type": "multipart/form-data" }, - }); - return response.data; -}; - -/** - * @description 프로필 이미지 업로드를 위한 useMutation 커스텀 훅 - */ -const useUploadProfileImagePublic = () => { - return useMutation({ - mutationFn: uploadProfileImagePublic, - onError: (error) => { - console.error("프로필 이미지 업로드 실패:", error); - toast.error("이미지 업로드에 실패했습니다."); - }, - }); -}; - -export default useUploadProfileImagePublic; diff --git a/src/api/mentee/client/queryKey.ts b/src/api/mentee/client/queryKey.ts deleted file mode 100644 index c57bd84a..00000000 --- a/src/api/mentee/client/queryKey.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum QueryKeys { - applyMentoringList = "applyMentoringList", -} diff --git a/src/api/mentee/client/useGetApplyMentoringList.ts b/src/api/mentee/client/useGetApplyMentoringList.ts deleted file mode 100644 index 153568c8..00000000 --- a/src/api/mentee/client/useGetApplyMentoringList.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { AxiosError } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentoringListItem } from "@/types/mentee"; -import { VerifyStatus } from "@/types/mentee"; - -import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; -import type { QueryFunctionContext } from "@tanstack/react-query"; - -interface UseGetApplyMentoringListResponse { - content: MentoringListItem[]; - nextPageNumber: number; -} -type UseGetApplyMentoringListRequest = VerifyStatus; - -const OFFSET = 3; // 기본 페이지 크기 - -const getApplyMentoringList = async ({ - queryKey, - pageParam, -}: QueryFunctionContext<[string, VerifyStatus], number>): Promise => { - const [, verifyStatus] = queryKey; - const res = await axiosInstance.get( - `/mentee/mentorings?verify-status=${verifyStatus}&size=${OFFSET}&page=${pageParam}`, - ); - return res.data; -}; - -const useGetApplyMentoringList = (verifyStatus: UseGetApplyMentoringListRequest) => { - return useInfiniteQuery< - UseGetApplyMentoringListResponse, - AxiosError, - MentoringListItem[], - [string, VerifyStatus], - number - >({ - queryKey: [QueryKeys.applyMentoringList, verifyStatus], - queryFn: getApplyMentoringList, - initialPageParam: 0, - getNextPageParam: (lastPage) => (lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber), - staleTime: 1000 * 60 * 5, // 5분간 캐시 - select: (data) => data.pages.flatMap((p) => p.content), - }); -}; - -// 멘토링 리스트 프리페치용 훅 -export const usePrefetchApplyMentoringList = () => { - const queryClient = useQueryClient(); - - const prefetchMenteeMentoringList = (verifyStatus: UseGetApplyMentoringListRequest) => { - queryClient.prefetchInfiniteQuery({ - queryKey: [QueryKeys.applyMentoringList, verifyStatus], - queryFn: getApplyMentoringList, - initialPageParam: 0, - staleTime: 1000 * 60 * 5, - }); - }; - - return { prefetchMenteeMentoringList }; -}; - -export default useGetApplyMentoringList; diff --git a/src/api/mentee/client/usePatchMenteeCheckMentorings.ts b/src/api/mentee/client/usePatchMenteeCheckMentorings.ts deleted file mode 100644 index 537b2b63..00000000 --- a/src/api/mentee/client/usePatchMenteeCheckMentorings.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AxiosError } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { useMutation } from "@tanstack/react-query"; - -interface UsePatchMenteeCheckMentoringsRequest { - checkedMentoringIds: number[]; -} - -interface UsePatchMenteeCheckMentoringsResponse { - checkedMentoringIds: number[]; -} - -const patchMenteeCheckMentorings = async ( - body: UsePatchMenteeCheckMentoringsRequest, -): Promise => { - const res = await axiosInstance.patch("/mentee/mentorings/check", body); - return res.data; -}; - -const usePatchMenteeCheckMentorings = () => - useMutation({ - mutationFn: patchMenteeCheckMentorings, - }); - -export default usePatchMenteeCheckMentorings; diff --git a/src/api/mentee/client/usePostApplyMentoring.ts b/src/api/mentee/client/usePostApplyMentoring.ts deleted file mode 100644 index e4e2c28b..00000000 --- a/src/api/mentee/client/usePostApplyMentoring.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface UsePostApplyMentoringRequest { - mentorId: number; -} -interface UsePostApplyMentoringResponse { - mentoringId: number; -} - -const postApplyMentoring = async (body: UsePostApplyMentoringRequest): Promise => { - const res = await axiosInstance.post("/mentee/mentorings", body); - return res.data; -}; - -const usePostApplyMentoring = () => { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: postApplyMentoring, - onSuccess: async () => { - // 멘토링 신청 후 멘토 목록을 새로고침 - await queryClient.invalidateQueries({ queryKey: [QueryKeys.applyMentoringList] }); - }, - onError: () => { - toast.error("멘토 신청에 실패했습니다. 다시 시도해주세요."); - }, - }); -}; - -export default usePostApplyMentoring; diff --git a/src/api/mentor/client/queryKey.ts b/src/api/mentor/client/queryKey.ts deleted file mode 100644 index 345ca144..00000000 --- a/src/api/mentor/client/queryKey.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum QueryKeys { - mentoringNewCount = "mentoringNewCount", - mentoringList = "mentoringList", - myMentorProfile = "myMentorProfile", -} diff --git a/src/api/mentor/client/useGetMentorMyProfile.ts b/src/api/mentor/client/useGetMentorMyProfile.ts deleted file mode 100644 index b8598b02..00000000 --- a/src/api/mentor/client/useGetMentorMyProfile.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentorCardPreview } from "@/types/mentor"; - -// 학업 학기 (예: "2026-1") -import { useQuery } from "@tanstack/react-query"; - -type UseGetMyMentorProfileResponse = MentorCardPreview; - -const getMentorMyProfile = async (): Promise => { - const res = await axiosInstance.get("/mentor/my"); - return res.data; -}; - -const useGetMentorMyProfile = () => { - return useQuery({ - queryKey: [QueryKeys.myMentorProfile], - queryFn: getMentorMyProfile, - staleTime: 1000 * 60 * 5, // 5분간 캐시 - }); -}; - -export default useGetMentorMyProfile; diff --git a/src/api/mentor/client/useGetMentoringList.ts b/src/api/mentor/client/useGetMentoringList.ts deleted file mode 100644 index 88024b75..00000000 --- a/src/api/mentor/client/useGetMentoringList.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentoringItem } from "@/types/mentor"; - -import { useInfiniteQuery } from "@tanstack/react-query"; - -interface UseGetMentoringListResponse { - content: MentoringItem[]; - nextPageNumber: number; -} -interface UseGetMentoringListRequest { - size?: number; -} -const OFFSET = 5; // 페이지 오프셋 초기값 - -const getMentoringList = async (page: number, size: number = OFFSET): Promise => { - const endpoint = `/mentor/mentorings?size=${size}&page=${page}`; - const res = await axiosInstance.get(endpoint); - return res.data; -}; - -// 무한스크롤을 위한 useInfiniteQuery -const useGetMentoringList = ({ size = OFFSET }: UseGetMentoringListRequest) => - useInfiniteQuery({ - queryKey: [QueryKeys.mentoringList, size], - queryFn: ({ pageParam = 0 }) => getMentoringList(pageParam as number, size), - initialPageParam: 0, - getNextPageParam: (lastPage: UseGetMentoringListResponse) => { - // nextPageNumber가 -1이면 더 이상 페이지가 없음 - return lastPage.nextPageNumber !== -1 ? lastPage.nextPageNumber : undefined; - }, - refetchInterval: 1000 * 60 * 10, // ⏱️ 10분마다 자동 재요청 - staleTime: 1000 * 60 * 5, // fresh 상태 유지 - select: (data) => data.pages.flatMap((page) => page.content), // 모든 페이지의 content를 평 - }); - -export default useGetMentoringList; diff --git a/src/api/mentor/client/useGetMentoringUncheckedCount.ts b/src/api/mentor/client/useGetMentoringUncheckedCount.ts deleted file mode 100644 index 3204769d..00000000 --- a/src/api/mentor/client/useGetMentoringUncheckedCount.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useQuery } from "@tanstack/react-query"; - -interface GetMentoringNewCountResponse { - uncheckedCount: number; // 멘토링 신규 요청 수 -} - -const getMentoringUncheckedCount = async (): Promise => { - const endpoint = "/mentor/mentorings/check"; - const res = await axiosInstance.get(endpoint); - return res.data; -}; - -// ISR 의도(10분) 유지: staleTime 10분 -const useGetMentoringUncheckedCount = (isEnable: boolean) => - useQuery({ - queryKey: [QueryKeys.mentoringNewCount], - queryFn: getMentoringUncheckedCount, - enabled: isEnable, - refetchInterval: 1000 * 60 * 10, // ⏱️ 10분마다 자동 재요청 - staleTime: 1000 * 60 * 5, // fresh 상태 유지 - select: (data) => data.uncheckedCount, // 필요한 데이터만 반환 - }); - -export default useGetMentoringUncheckedCount; diff --git a/src/api/mentor/client/usePatchApprovalStatus.ts b/src/api/mentor/client/usePatchApprovalStatus.ts deleted file mode 100644 index 50bb68c4..00000000 --- a/src/api/mentor/client/usePatchApprovalStatus.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentoringApprovalStatus } from "@/types/mentor"; - -import { customAlert } from "@/lib/zustand/useAlertModalStore"; -import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; -import { IconSmile, IconUnSmile } from "@/public/svgs/mentor"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface UsePatchApprovalStatusRequest { - status: MentoringApprovalStatus; - mentoringId: number; // 멘토링 ID -} -interface UsePatchApprovalStatusResponse { - mentoringId: number; // 멘토링 ID - chatRoomId: number; // 채팅방 ID -} - -const patchApprovalStatus = async (props: UsePatchApprovalStatusRequest): Promise => { - const { status, mentoringId } = props; - const res = await axiosInstance.patch(`/mentor/mentorings/${mentoringId}`, { - status, - }); - return res.data; -}; - -const usePatchApprovalStatus = () => { - const router = useRouter(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: patchApprovalStatus, - onSuccess: async (data, variables) => { - // 멘토링 상태 변경 후 쿼리 무효화 - await Promise.all([ - queryClient.invalidateQueries({ queryKey: [QueryKeys.mentoringList] }), - queryClient.invalidateQueries({ queryKey: [QueryKeys.mentoringNewCount] }), - ]); - - if (variables.status === MentoringApprovalStatus.REJECTED) { - customAlert({ - title: "멘티 신청을 거절했어요.", - icon: IconUnSmile, - content: "현재까지 누적해서 거절했어요. 누적 5회 거절 시 활동에 제약이 있으니 유의해주세요.", - buttonText: "닫기", - }); - } else if (variables.status === MentoringApprovalStatus.APPROVED) { - const ok = await customConfirm({ - title: "멘티 신청이 완료되었어요!", - content: "지금 바로 멘티에게 메시지를 전송해보세요", - icon: IconSmile, - rejectMessage: "닫기", - approveMessage: "1:1 채팅 바로가기", - }); - if (ok) { - router.push(`/mentor/chat/${data.chatRoomId}`); - } - } - }, - onError: (error) => { - customAlert({ - title: "멘토링 상태 변경 실패", - content: "멘토링 상태 변경 중 오류가 발생했습니다. 다시 시도해주세요.", - buttonText: "확인", - }); - }, - }); -}; - -export default usePatchApprovalStatus; diff --git a/src/api/mentor/client/usePatchMentorCheckMentorings.ts b/src/api/mentor/client/usePatchMentorCheckMentorings.ts deleted file mode 100644 index 3eb2126a..00000000 --- a/src/api/mentor/client/usePatchMentorCheckMentorings.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface UsePatchMentorCheckMentoringsRequest { - checkedMentoringIds: number[]; -} -interface UsePatchMentorCheckMentoringsResponse { - checkedMentoringIds: number[]; // 체크된 멘토링 ID 배열 -} - -const patchMenotrCheck = async ( - body: UsePatchMentorCheckMentoringsRequest, -): Promise => { - const res = await axiosInstance.patch("/mentor/mentorings/check", body); - return res.data; -}; - -const usePatchMentorCheckMentorings = () => { - const queriesClient = useQueryClient(); - return useMutation({ - onSuccess: () => { - // 멘토링 체크 상태 변경 후 멘토링 목록 쿼리 무효화 - Promise.all([ - queriesClient.invalidateQueries({ queryKey: [QueryKeys.mentoringList] }), - queriesClient.invalidateQueries({ queryKey: [QueryKeys.mentoringNewCount] }), - ]); - }, - mutationFn: patchMenotrCheck, - }); -}; - -export default usePatchMentorCheckMentorings; diff --git a/src/api/mentor/client/usePostMentorApplication.ts b/src/api/mentor/client/usePostMentorApplication.ts deleted file mode 100644 index fea8ef24..00000000 --- a/src/api/mentor/client/usePostMentorApplication.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -export interface PostMentorApplicationRequest { - interestedCountries: string[]; // 관심 국가 목록 - country: string; // 수학 국가 - universityName: string; // 수학 학교 - studyStatus: "STUDYING" | "PLANNING" | "COMPLETED"; // 준비 단계 - verificationFile: File; // 증명서 파일 -} - -const postMentorApplication = async (body: PostMentorApplicationRequest): Promise => { - const formData = new FormData(); - - // JSON 데이터를 Blob으로 추가 - const applicationData = { - interestedCountries: body.interestedCountries, - country: body.country, - universityName: body.universityName, - studyStatus: body.studyStatus, - }; - - formData.append( - "mentorApplicationRequest", - new Blob([JSON.stringify(applicationData)], { type: "application/json" }), - ); - - // 파일 추가 - formData.append("file", body.verificationFile); - - const res = await axiosInstance.post("/mentor/verification", formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }); - - return res.data; -}; - -const usePostMentorApplication = () => { - return useMutation({ - mutationFn: postMentorApplication, - onError: (error) => { - toast.error("멘토 신청에 실패했습니다. 다시 시도해주세요."); - }, - }); -}; - -export default usePostMentorApplication; diff --git a/src/api/mentor/client/usePutMyMentorProfile.ts b/src/api/mentor/client/usePutMyMentorProfile.ts deleted file mode 100644 index 8de00810..00000000 --- a/src/api/mentor/client/usePutMyMentorProfile.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface ChannelPayload { - type: string; - url: string; -} - -export interface PutMyMentorProfileRequest { - channels: ChannelPayload[]; - passTip: string; - introduction: string; -} - -const putMyMentorProfile = async (body: PutMyMentorProfileRequest): Promise => { - const res = await axiosInstance.put("/mentor/my", body); - return res.data; -}; - -const usePutMyMentorProfile = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: putMyMentorProfile, - onSuccess: () => { - // 멘토 프로필 데이터를 stale로 만들어 다음 요청 시 새로운 데이터를 가져오도록 함 - queryClient.invalidateQueries({ - queryKey: [QueryKeys.myMentorProfile], - }); - }, - }); -}; - -export default usePutMyMentorProfile; diff --git a/src/api/mentors/client/queryKey.ts b/src/api/mentors/client/queryKey.ts deleted file mode 100644 index 39622908..00000000 --- a/src/api/mentors/client/queryKey.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum QueryKeys { - mentorDetail = "mentorDetail", - mentoringNewCount = "mentoringNewCount", - mentorList = "mentorList", - myMentorProfile = "myMentorProfile", - recommendedMentor = "recommendedMentor", - menteeMentoringList = "menteeMentoringList", -} diff --git a/src/api/mentors/client/useGetMentorDetail.ts b/src/api/mentors/client/useGetMentorDetail.ts deleted file mode 100644 index af4225a6..00000000 --- a/src/api/mentors/client/useGetMentorDetail.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentorCardDetail } from "@/types/mentor"; - -import { useQuery } from "@tanstack/react-query"; - -type UseGetMentorDetailResponse = MentorCardDetail; - -const getMentorDetail = async ({ queryKey }: { queryKey: [string, number] }): Promise => { - const [, mentorId] = queryKey; - const res = await axiosInstance.get(`/mentors/${mentorId}`); - return res.data; -}; - -const useGetMentorDetail = (mentorId: number | null) => { - return useQuery({ - queryKey: [QueryKeys.mentorDetail, mentorId!], - queryFn: getMentorDetail, - enabled: mentorId !== null, - staleTime: 1000 * 60 * 5, // 5분간 캐시 - }); -}; - -export default useGetMentorDetail; diff --git a/src/api/mentors/client/useGetMentorList.ts b/src/api/mentors/client/useGetMentorList.ts deleted file mode 100644 index fcac4fbb..00000000 --- a/src/api/mentors/client/useGetMentorList.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { MentorCardDetail } from "@/types/mentor"; - -import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; -import type { QueryFunctionContext } from "@tanstack/react-query"; - -interface UseGetMentorListRequest { - region?: string; -} - -interface GetMentorListResponse { - /** 다음 페이지 번호. 다음 페이지가 없으면 -1 */ - nextPageNumber: number; - content: MentorCardDetail[]; -} - -const OFFSET = 10; // 기본 페이지 크기 - -const getMentorList = async ({ - queryKey, - pageParam, -}: QueryFunctionContext<[string, string], number>): Promise => { - const [, region] = queryKey; - const res = await axiosInstance.get( - `/mentors?region=${region}&page=${pageParam}&size=${OFFSET}`, - ); - return res.data; -}; - -const useGetMentorList = ({ region = "" }: UseGetMentorListRequest = {}) => - useInfiniteQuery({ - queryKey: [QueryKeys.mentorList, region], - queryFn: getMentorList, - initialPageParam: 0, - getNextPageParam: (lastPage) => (lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber), - staleTime: 1000 * 60 * 5, - select: (data) => data.pages.flatMap((p) => p.content), - }); - -// 탭 프리페치용 훅 -export const usePrefetchMentorList = () => { - const queryClient = useQueryClient(); - - const prefetchMentorList = (region: string) => { - queryClient.prefetchInfiniteQuery({ - queryKey: [QueryKeys.mentorList, region], - queryFn: getMentorList, - initialPageParam: 0, - staleTime: 1000 * 60 * 5, - }); - }; - - return { prefetchMentorList }; -}; - -export default useGetMentorList; diff --git a/src/api/my/client/queryKey.ts b/src/api/my/client/queryKey.ts deleted file mode 100644 index 6dff8450..00000000 --- a/src/api/my/client/queryKey.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum QueryKeys { - myInfo = "myInfo", -} diff --git a/src/api/my/client/useGetMyInfo.ts b/src/api/my/client/useGetMyInfo.ts deleted file mode 100644 index 9f6acd36..00000000 --- a/src/api/my/client/useGetMyInfo.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { UserRole } from "@/types/mentor"; -import { BaseUserInfo } from "@/types/myInfo"; - -import { useMutationState, useQuery } from "@tanstack/react-query"; - -// --- 타입 정의 --- -export interface MenteeInfo extends BaseUserInfo { - role: UserRole.MENTEE; - interestedCountries: string[]; -} - -export interface MentorInfo extends BaseUserInfo { - role: UserRole.MENTOR; - attendedUniversity: string; -} - -export interface AdminInfo extends BaseUserInfo { - role: UserRole.ADMIN; - attendedUniversity: string; -} - -export type MyInfoResponse = MenteeInfo | MentorInfo | AdminInfo; - -// --- API 호출 함수 --- -const getMyInfo = async (): Promise => { - const response: AxiosResponse = await axiosInstance.get("/my"); - return response.data; -}; - -const useGetMyInfo = () => { - const queryResult = useQuery({ - queryKey: [QueryKeys.myInfo], - queryFn: getMyInfo, - // staleTime을 무한으로 설정하여 불필요한 자동 refetch를 방지합니다. - staleTime: Infinity, - gcTime: 1000 * 60 * 30, // 예: 30분 - }); - - const pendingMutations = useMutationState({ - filters: { - mutationKey: [QueryKeys.myInfo, "patch"], - status: "pending", - }, - select: (mutation) => { - return mutation.state.variables as Partial; - }, - }); - - const isOptimistic = pendingMutations.length > 0; - const pendingData = isOptimistic ? pendingMutations[0] : null; - - const displayData = isOptimistic ? { ...queryResult.data, ...pendingData } : queryResult.data; - - return { ...queryResult, data: displayData }; -}; - -export default useGetMyInfo; diff --git a/src/api/my/client/usePatchMyInfo.ts b/src/api/my/client/usePatchMyInfo.ts deleted file mode 100644 index 4a61f1fa..00000000 --- a/src/api/my/client/usePatchMyInfo.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AxiosError } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export interface UseMyMentorProfileRequest { - nickname?: string; - file?: File; -} - -const patchMyMentorProfile = async (data: UseMyMentorProfileRequest): Promise => { - const formData = new FormData(); - if (data.nickname) { - formData.append("nickname", data.nickname); - } - if (data.file) { - formData.append("file", data.file); - } - - const res = await axiosInstance.patch("/my", formData); - - return res.data; -}; - -const usePatchMyInfo = () => { - const queryClient = useQueryClient(); - - return useMutation, UseMyMentorProfileRequest>({ - mutationKey: [QueryKeys.myInfo, "patch"], - mutationFn: patchMyMentorProfile, - onSettled: () => { - queryClient.invalidateQueries({ - queryKey: [QueryKeys.myInfo], - }); - }, - onSuccess: () => { - toast.success("프로필이 성공적으로 수정되었습니다."); - }, - onError: (error) => { - const errorMessage = error.response?.data?.message; - toast.error(errorMessage || "프로필 수정에 실패했습니다. 다시 시도해주세요."); - }, - }); -}; - -export default usePatchMyInfo; diff --git a/src/api/my/client/usePatchMyPassword.ts b/src/api/my/client/usePatchMyPassword.ts deleted file mode 100644 index 05fabd8c..00000000 --- a/src/api/my/client/usePatchMyPassword.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosError } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import useAuthStore from "@/lib/zustand/useAuthStore"; -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -export interface UseMyMentorProfileRequest { - currentPassword: string; - newPassword: string; - newPasswordConfirmation: string; -} - -const patchMyPassword = async (data: UseMyMentorProfileRequest): Promise => { - const res = await axiosInstance.patch("/my/password", data, {}); - return res.data; -}; - -const usePatchMyPassword = () => { - const queryClient = useQueryClient(); - const router = useRouter(); - const { clearAccessToken } = useAuthStore(); - - return useMutation, UseMyMentorProfileRequest>({ - mutationKey: [QueryKeys.myInfo, "password", "patch"], - mutationFn: patchMyPassword, - onSuccess: () => { - clearAccessToken(); - queryClient.clear(); - toast.success("프로필이 성공적으로 수정되었습니다."); - router.replace("/"); - }, - onError: (error) => { - const errorMessage = error.response?.data?.message; - toast.error(errorMessage || "비밀번호 변경에 실패했습니다. 다시 시도해주세요."); - }, - }); -}; - -export default usePatchMyPassword; diff --git a/src/api/news/client/queryKey.ts b/src/api/news/client/queryKey.ts deleted file mode 100644 index 9065e83d..00000000 --- a/src/api/news/client/queryKey.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum QueryKeys { - articleList = "articleList", - postAddArticle = "postAddArticle", - putModifyArticle = "putModifyArticle", -} diff --git a/src/api/news/client/useDeleteArticle.ts b/src/api/news/client/useDeleteArticle.ts deleted file mode 100644 index 81e4f936..00000000 --- a/src/api/news/client/useDeleteArticle.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -// Article 타입 import 필요 - -// 1. 롤백을 위해 이전 데이터를 저장할 context 타입을 정의합니다. -type ArticleDeleteMutationContext = { - previousArticleList?: Article[]; -}; - -const deleteArticle = async (articleId: number): Promise => { - const response: AxiosResponse = await axiosInstance.delete(`/news/${articleId}`); - return response.data; -}; - -// 2. 어떤 유저의 목록을 업데이트할지 식별하기 위해 userId를 props로 받습니다. -const useDeleteArticle = (userId: number | null) => { - const queryClient = useQueryClient(); - const queryKey = [QueryKeys.articleList, userId]; - - return useMutation< - void, // 성공 시 반환 타입 - AxiosError<{ message: string }>, // 에러 타입 - number, // mutate 함수에 전달하는 변수 타입 (articleId) - ArticleDeleteMutationContext // context 타입 - >({ - mutationFn: deleteArticle, - - onMutate: async (deletedArticleId) => { - await queryClient.cancelQueries({ queryKey }); - - const previousArticleList = queryClient.getQueryData(queryKey); - - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, (oldData) => { - if (!oldData) return { newsResponseList: [] }; - return { - newsResponseList: oldData.newsResponseList.filter((article) => article.id !== deletedArticleId), - }; - }); - - return { previousArticleList }; - }, - - onError: (error, variables, context) => { - if (context?.previousArticleList) { - queryClient.setQueryData(queryKey, context.previousArticleList); - } - toast.error("아티클 삭제에 실패했습니다. 다시 시도해주세요."); - console.error("Failed to delete article:", error); - }, - - onSettled: () => { - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; - -export default useDeleteArticle; diff --git a/src/api/news/client/useDeleteArticleLike.ts b/src/api/news/client/useDeleteArticleLike.ts deleted file mode 100644 index cda01e76..00000000 --- a/src/api/news/client/useDeleteArticleLike.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -// Article 타입에 isLiked, likeCount 속성이 포함되어 있어야 합니다. - -interface DeleteArticleLikeResponse { - isLiked: boolean; - likeCount: number; -} - -// 1. 롤백을 위한 context 타입을 정의합니다. -type ArticleLikeMutationContext = { - previousArticleList?: { newsResponseList: Article[] }; -}; - -const deleteArticleLike = async (articleId: number): Promise => { - const response: AxiosResponse = await axiosInstance.delete(`/news/${articleId}/like`); - return response.data; -}; - -// 2. 어떤 유저의 목록을 업데이트할지 식별하기 위해 userId를 props로 받습니다. -const useDeleteArticleLike = (userId: number | null) => { - const queryClient = useQueryClient(); - const queryKey = [QueryKeys.articleList, userId]; - - return useMutation< - DeleteArticleLikeResponse, - AxiosError<{ message: string }>, - number, // mutate 함수에 전달하는 변수 타입 (articleId) - ArticleLikeMutationContext - >({ - mutationFn: deleteArticleLike, - - // 3. onMutate: '좋아요 취소' 클릭 즉시 UI를 업데이트합니다. - onMutate: async (unlikedArticleId) => { - await queryClient.cancelQueries({ queryKey }); - - const previousArticleList = queryClient.getQueryData<{ newsResponseList: Article[] }>(queryKey); - - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, (oldData) => { - if (!oldData) return { newsResponseList: [] }; - return { - newsResponseList: oldData.newsResponseList.map((article) => - article.id === unlikedArticleId - ? { - ...article, - isLiked: false, - likeCount: Math.max(0, (article.likeCount ?? 1) - 1), - } - : article, - ), - }; - }); - - return { previousArticleList }; - }, - - // 4. onError: 실패 시 이전 상태로 롤백합니다. - onError: (err, variables, context) => { - if (context?.previousArticleList) { - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, context.previousArticleList); - } - }, - }); -}; - -export default useDeleteArticleLike; diff --git a/src/api/news/client/useGetArticleList.ts b/src/api/news/client/useGetArticleList.ts deleted file mode 100644 index 77cf86cc..00000000 --- a/src/api/news/client/useGetArticleList.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { useQuery } from "@tanstack/react-query"; - -interface ArticleListResponse { - newsResponseList: Article[]; -} - -const getArticleList = async (userId: number): Promise => { - const response: AxiosResponse = await axiosInstance.get(`/news?author-id=${userId}`); - return response.data; -}; - -const useGetArticleList = (userId: number) => { - return useQuery({ - queryKey: [QueryKeys.articleList, userId], - queryFn: () => { - // enabled 옵션이 있더라도, 타입 가드를 추가하면 더 안전합니다. - if (userId === null) { - return Promise.reject(new Error("User ID is null")); - } - return getArticleList(userId); - }, - staleTime: 1000 * 60 * 10, // ⏱️ 10분 - - enabled: userId !== null, - // 서버 응답(ArticleListResponse)에서 실제 데이터 배열(Article[])만 선택합니다. - select: (data) => data.newsResponseList, - }); -}; - -export default useGetArticleList; diff --git a/src/api/news/client/usePostAddArticle.ts b/src/api/news/client/usePostAddArticle.ts deleted file mode 100644 index bee9a3db..00000000 --- a/src/api/news/client/usePostAddArticle.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { ArticleFormData } from "@/components/mentor/ArticleBottomSheetModal/lib/schema"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { toast } from "@/lib/zustand/useToastStore"; -import ArticleThumbUrlPng from "@/public/images/article-thumb.png"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -type ArticleMutationContext = { - previousArticleContainer?: { newsResponseList: Article[] }; -}; - -type UsePostAddArticleRequest = ArticleFormData; - -const postAddArticle = async (body: UsePostAddArticleRequest): Promise
=> { - const newsCreateRequest = { - title: body.title, - description: body.description, - url: body.url || "", - }; - - const formData = new FormData(); - formData.append("newsCreateRequest", new Blob([JSON.stringify(newsCreateRequest)], { type: "application/json" })); - if (body.file) { - formData.append("file", body.file); - } - const response: AxiosResponse
= await axiosInstance.post("/news", formData); - return response.data; -}; - -const usePostAddArticle = (userId: number | null) => { - const queryClient = useQueryClient(); - const queryKey = [QueryKeys.articleList, userId]; - - return useMutation, UsePostAddArticleRequest, ArticleMutationContext>({ - mutationFn: postAddArticle, - onMutate: async (newArticle) => { - await queryClient.cancelQueries({ queryKey }); - - const previousArticleContainer = queryClient.getQueryData<{ newsResponseList: Article[] }>(queryKey); - - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, (oldData) => { - if (!oldData) return { newsResponseList: [] }; - - const optimisticArticle: Article = { - id: Date.now(), // 임시 ID - title: newArticle.title, - description: newArticle.description, - url: newArticle.url || "", - thumbnailUrl: newArticle.file ? URL.createObjectURL(newArticle.file) : ArticleThumbUrlPng.src, - updatedAt: new Date().toISOString(), - }; - - return { - newsResponseList: [optimisticArticle, ...oldData.newsResponseList], - }; - }); - return { previousArticleContainer }; - }, - onError: (error, variables, context) => { - const errorMessage = error.response?.data?.message || ""; - if (context?.previousArticleContainer) { - queryClient.setQueryData(queryKey, context.previousArticleContainer); - } - toast.error("아티클 추가에 실패했습니다: " + errorMessage); - }, - onSettled: () => { - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; - -export default usePostAddArticle; diff --git a/src/api/news/client/usePostArticleLike.ts b/src/api/news/client/usePostArticleLike.ts deleted file mode 100644 index 5d452cce..00000000 --- a/src/api/news/client/usePostArticleLike.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -// Article 타입 import - -interface PostArticleLikeResponse { - isLiked: boolean; - likeCount: number; -} - -// 1. 롤백을 위한 context 타입을 정의합니다. -type ArticleLikeMutationContext = { - previousArticleList?: Article[]; -}; - -const postArticleLike = async (articleId: number): Promise => { - const response: AxiosResponse = await axiosInstance.post(`/news/${articleId}/like`); - return response.data; -}; - -// 2. 어떤 유저의 목록을 업데이트할지 식별하기 위해 userId를 props로 받습니다. -const usePostArticleLike = (userId: number | null) => { - const queryClient = useQueryClient(); - const queryKey = [QueryKeys.articleList, userId]; - - return useMutation< - PostArticleLikeResponse, - AxiosError<{ message: string }>, - number, // mutate 함수에 전달하는 변수 타입 (articleId) - ArticleLikeMutationContext - >({ - mutationFn: postArticleLike, - - // 3. onMutate: '좋아요' 클릭 즉시 UI를 업데이트합니다. - onMutate: async (likedArticleId) => { - await queryClient.cancelQueries({ queryKey }); - - const previousArticleList = queryClient.getQueryData(queryKey); - - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, (oldData) => { - if (!oldData) return { newsResponseList: [] }; - return { - newsResponseList: oldData.newsResponseList.map((article) => - article.id === likedArticleId - ? { - ...article, - isLiked: true, // '좋아요' 상태를 true로 변경 - likeCount: (article.likeCount ?? 0) + 1, // '좋아요' 수를 1 증가 - } - : article, - ), - }; - }); - - return { previousArticleList }; - }, - - // 4. onError: 실패 시 이전 상태로 롤백합니다. - onError: (err, variables, context) => { - if (context?.previousArticleList) { - queryClient.setQueryData(queryKey, context.previousArticleList); - } - }, - }); -}; - -export default usePostArticleLike; diff --git a/src/api/news/client/usePutModifyArticle.ts b/src/api/news/client/usePutModifyArticle.ts deleted file mode 100644 index 92d00ce5..00000000 --- a/src/api/news/client/usePutModifyArticle.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { AxiosError, AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { ArticleFormData } from "@/components/mentor/ArticleBottomSheetModal/lib/schema"; - -import { QueryKeys } from "./queryKey"; - -import { Article } from "@/types/news"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -type UsePutModifyArticleRequest = { - body: ArticleFormData & { isImageDeleted?: boolean }; - articleId: number; -}; -type ArticleMutationContext = { - previousArticleList?: Article[]; -}; - -const putModifyArticle = async (props: UsePutModifyArticleRequest): Promise
=> { - const { body, articleId } = props; - const newsUpdateRequest = { - title: body.title, - description: body.description, - url: body.url || "", - resetToDefaultImage: body.isImageDeleted === true, - }; - const formData = new FormData(); - formData.append("newsUpdateRequest", new Blob([JSON.stringify(newsUpdateRequest)], { type: "application/json" })); - if (body.file) formData.append("file", body.file); - - const response: AxiosResponse
= await axiosInstance.put(`/news/${articleId}`, formData); - return response.data; -}; - -const usePutModifyArticle = (userId: number | null) => { - const queryClient = useQueryClient(); - const queryKey = [QueryKeys.articleList, userId]; - - return useMutation, UsePutModifyArticleRequest, ArticleMutationContext>({ - mutationFn: putModifyArticle, - onMutate: async (variables) => { - await queryClient.cancelQueries({ queryKey }); - const previousArticleList = queryClient.getQueryData(queryKey); - - queryClient.setQueryData<{ newsResponseList: Article[] }>(queryKey, (oldData) => { - if (!oldData) return { newsResponseList: [] }; - - return { - newsResponseList: oldData.newsResponseList.map((article) => { - if (article.id === variables.articleId) { - const optimisticData = variables.body; - - return { - ...article, - title: optimisticData.title, - description: optimisticData.description, - url: optimisticData.url || "", - thumbnailUrl: optimisticData.file ? URL.createObjectURL(optimisticData.file) : article.thumbnailUrl, - }; - } - return article; - }), - }; - }); - return { previousArticleList }; - }, - onError: (error, variables, context) => { - const errorMessage = error.response?.data?.message || ""; - if (context?.previousArticleList) { - queryClient.setQueryData(queryKey, context.previousArticleList); - } - toast.error("아티클 수정에 실패했습니다." + errorMessage); - }, - onSettled: () => { - queryClient.invalidateQueries({ queryKey }); - }, - }); -}; - -export default usePutModifyArticle; diff --git a/src/api/reports/client/usePostReport.ts b/src/api/reports/client/usePostReport.ts deleted file mode 100644 index 28ee9fe5..00000000 --- a/src/api/reports/client/usePostReport.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { ReportType } from "@/types/reports"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation } from "@tanstack/react-query"; - -interface UsePostReportsRequest { - targetType: "POST"; // 지금은 게시글 신고 기능만 존재 - targetId: number; // 신고하려는 리소스의 ID - reportType: ReportType; // Docs 참고 -} - -const postReports = async (body: UsePostReportsRequest): Promise => { - const response: AxiosResponse = await axiosInstance.post(`/reports`, body); - return response.data; -}; - -const usePostReports = () => { - const router = useRouter(); - return useMutation({ - mutationFn: postReports, - onSuccess: () => { - toast.success("신고가 성공적으로 등록되었습니다."); - router.back(); // 신고 후 뒤로 이동 - }, - onError: (error) => { - toast.error("신고 등록에 실패했습니다. 잠시 후 다시 시도해주세요."); - }, - }); -}; - -export default usePostReports; diff --git a/src/api/score/client/queryKey.ts b/src/api/score/client/queryKey.ts deleted file mode 100644 index c7281e50..00000000 --- a/src/api/score/client/queryKey.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum QueryKeys { - myGpaScore = "myGpaScore", - myLanguageTestScore = "myLanguageTestScore", -} diff --git a/src/api/score/client/useGetMyGpaScore.ts b/src/api/score/client/useGetMyGpaScore.ts deleted file mode 100644 index 63d22266..00000000 --- a/src/api/score/client/useGetMyGpaScore.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { GpaScore } from "@/types/score"; - -import { useQuery } from "@tanstack/react-query"; - -interface UseMyGpaScoreResponse { - gpaScoreStatusResponseList: GpaScore[]; -} - -export const getMyGpaScore = (): Promise> => axiosInstance.get("/scores/gpas"); - -const useGetMyGpaScore = () => { - return useQuery({ - queryKey: [QueryKeys.myGpaScore], - queryFn: getMyGpaScore, - staleTime: Infinity, // 5분간 캐시 - select: (data) => data.data.gpaScoreStatusResponseList, - }); -}; - -export default useGetMyGpaScore; diff --git a/src/api/score/client/useGetMyLanguageTestScore.ts b/src/api/score/client/useGetMyLanguageTestScore.ts deleted file mode 100644 index b44cd762..00000000 --- a/src/api/score/client/useGetMyLanguageTestScore.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { LanguageTestScore } from "@/types/score"; - -import { useQuery } from "@tanstack/react-query"; - -interface UseGetMyLanguageTestScoreResponse { - languageTestScoreStatusResponseList: LanguageTestScore[]; -} - -export const getMyLanguageTestScore = (): Promise> => - axiosInstance.get("/scores/language-tests"); - -const useGetMyLanguageTestScore = () => { - return useQuery({ - queryKey: [QueryKeys.myLanguageTestScore], - queryFn: getMyLanguageTestScore, - staleTime: Infinity, - select: (data) => data.data.languageTestScoreStatusResponseList, - }); -}; - -export default useGetMyLanguageTestScore; diff --git a/src/api/score/client/usePostGpaScore.ts b/src/api/score/client/usePostGpaScore.ts deleted file mode 100644 index f4852529..00000000 --- a/src/api/score/client/usePostGpaScore.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -import { QueryKeys } from "./queryKey"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -interface UsePostGpaScoreRequest { - gpaScoreRequest: { - gpa: number; - gpaCriteria: number; - issueDate: string; // yyyy-MM-dd - }; - file: Blob; -} - -export const postGpaScore = (request: UsePostGpaScoreRequest): Promise> => { - const convertedRequest: FormData = new FormData(); - convertedRequest.append( - "gpaScoreRequest", - new Blob([JSON.stringify(request.gpaScoreRequest)], { type: "application/json" }), - ); - convertedRequest.append("file", request.file); - return axiosInstance.post("/scores/gpas", convertedRequest); -}; - -export const usePostGpaScore = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (request: UsePostGpaScoreRequest) => postGpaScore(request), - - onSuccess: () => { - toast.success("학점 정보가 성공적으로 제출되었습니다."); - queryClient.invalidateQueries({ queryKey: [QueryKeys.myGpaScore] }); - }, - - onError: (error) => { - console.error("학점 제출 중 오류 발생:", error); - toast.error("오류가 발생했습니다. 다시 시도해주세요."); - }, - }); -}; diff --git a/src/api/score/client/usePostLanguageTestScore.ts b/src/api/score/client/usePostLanguageTestScore.ts deleted file mode 100644 index adb992e6..00000000 --- a/src/api/score/client/usePostLanguageTestScore.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useRouter } from "next/navigation"; - -import { AxiosResponse } from "axios"; - -import { axiosInstance } from "@/utils/axiosInstance"; - -// 예시: 성공 후 페이지 이동 -import { QueryKeys } from "./queryKey"; - -import { LanguageTestEnum } from "@/types/score"; - -import { toast } from "@/lib/zustand/useToastStore"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -// QueryKeys가 정의된 경로로 수정해주세요. - -interface UsePostLanguageTestScoreRequest { - languageTestScoreRequest: { - languageTestType: LanguageTestEnum; - languageTestScore: string; - issueDate: string; // yyyy-MM-dd - }; - file: File; -} - -export const postLanguageTestScore = (request: UsePostLanguageTestScoreRequest): Promise> => { - const convertedRequest: FormData = new FormData(); - convertedRequest.append( - "languageTestScoreRequest", - new Blob([JSON.stringify(request.languageTestScoreRequest)], { type: "application/json" }), - ); - convertedRequest.append("file", request.file); - return axiosInstance.post("/scores/language-tests", convertedRequest); -}; -/** - * 공인 어학 점수를 제출(POST)하기 위한 useMutation 훅입니다. - */ -export const usePostLanguageTestScore = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (request: UsePostLanguageTestScoreRequest) => postLanguageTestScore(request), - - onSuccess: () => { - toast.success("어학 성적이 성공적으로 제출되었습니다."); - queryClient.invalidateQueries({ queryKey: [QueryKeys.myLanguageTestScore] }); - }, - - onError: (error) => { - console.error("어학 성적 제출 중 오류 발생:", error); - toast.error("오류가 발생했습니다. 다시 시도해주세요."); - }, - }); -}; diff --git a/src/apis/Auth/api.ts b/src/apis/Auth/api.ts index 21023614..dd7c87f9 100644 --- a/src/apis/Auth/api.ts +++ b/src/apis/Auth/api.ts @@ -142,4 +142,4 @@ export const authApi = { const res = await publicAxiosInstance.post(`/auth/sign-up`, payload); return res.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/Auth/deleteAccount.ts b/src/apis/Auth/deleteAccount.ts index 2c0c24ba..b9274190 100644 --- a/src/apis/Auth/deleteAccount.ts +++ b/src/apis/Auth/deleteAccount.ts @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation"; import { AxiosError } from "axios"; -import { authApi, AccountResponse } from "./api"; +import { AccountResponse, authApi } from "./api"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; @@ -33,4 +33,4 @@ const useDeleteUserAccount = () => { }); }; -export default useDeleteUserAccount; \ No newline at end of file +export default useDeleteUserAccount; diff --git a/src/apis/Auth/postAppleAuth.ts b/src/apis/Auth/postAppleAuth.ts index 6289fd1b..2ef03e0a 100644 --- a/src/apis/Auth/postAppleAuth.ts +++ b/src/apis/Auth/postAppleAuth.ts @@ -4,7 +4,7 @@ import { AxiosError } from "axios"; import { validateSafeRedirect } from "@/utils/authUtils"; -import { authApi, AppleAuthResponse, AppleAuthRequest } from "./api"; +import { AppleAuthRequest, AppleAuthResponse, authApi } from "./api"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; @@ -43,4 +43,4 @@ const usePostAppleAuth = () => { }); }; -export default usePostAppleAuth; \ No newline at end of file +export default usePostAppleAuth; diff --git a/src/apis/Auth/postEmailLogin.ts b/src/apis/Auth/postEmailLogin.ts index 90c0bce0..af5d54ce 100644 --- a/src/apis/Auth/postEmailLogin.ts +++ b/src/apis/Auth/postEmailLogin.ts @@ -4,7 +4,7 @@ import { AxiosError } from "axios"; import { validateSafeRedirect } from "@/utils/authUtils"; -import { authApi, EmailLoginResponse, EmailLoginRequest } from "./api"; +import { EmailLoginRequest, EmailLoginResponse, authApi } from "./api"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; @@ -37,4 +37,4 @@ const usePostEmailAuth = () => { }); }; -export default usePostEmailAuth; \ No newline at end of file +export default usePostEmailAuth; diff --git a/src/apis/Auth/postEmailVerification.ts b/src/apis/Auth/postEmailVerification.ts index 059f5d69..c94bc09d 100644 --- a/src/apis/Auth/postEmailVerification.ts +++ b/src/apis/Auth/postEmailVerification.ts @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; -import { authApi, EmailSignUpResponse, EmailSignUpRequest } from "./api"; +import { EmailSignUpRequest, EmailSignUpResponse, authApi } from "./api"; import { toast } from "@/lib/zustand/useToastStore"; import { useMutation } from "@tanstack/react-query"; @@ -18,4 +18,4 @@ const usePostEmailSignUp = () => { }); }; -export default usePostEmailSignUp; \ No newline at end of file +export default usePostEmailSignUp; diff --git a/src/apis/Auth/postKakaoAuth.ts b/src/apis/Auth/postKakaoAuth.ts index 22676df0..9179848b 100644 --- a/src/apis/Auth/postKakaoAuth.ts +++ b/src/apis/Auth/postKakaoAuth.ts @@ -4,7 +4,7 @@ import { AxiosError } from "axios"; import { validateSafeRedirect } from "@/utils/authUtils"; -import { authApi, KakaoAuthResponse, KakaoAuthRequest } from "./api"; +import { KakaoAuthRequest, KakaoAuthResponse, authApi } from "./api"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; @@ -44,4 +44,4 @@ const usePostKakaoAuth = () => { }); }; -export default usePostKakaoAuth; \ No newline at end of file +export default usePostKakaoAuth; diff --git a/src/apis/Auth/postSignOut.ts b/src/apis/Auth/postSignOut.ts index f0674f53..1c477418 100644 --- a/src/apis/Auth/postSignOut.ts +++ b/src/apis/Auth/postSignOut.ts @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; -import { authApi, SignOutResponse } from "./api"; +import { SignOutResponse, authApi } from "./api"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -24,4 +24,4 @@ const usePostLogout = () => { }); }; -export default usePostLogout; \ No newline at end of file +export default usePostLogout; diff --git a/src/apis/Auth/postSignUp.ts b/src/apis/Auth/postSignUp.ts index 161e0b04..cbfe6aae 100644 --- a/src/apis/Auth/postSignUp.ts +++ b/src/apis/Auth/postSignUp.ts @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; -import { authApi, SignUpResponse, SignUpRequest } from "./api"; +import { SignUpRequest, SignUpResponse, authApi } from "./api"; import { toast } from "@/lib/zustand/useToastStore"; import { useMutation } from "@tanstack/react-query"; @@ -18,4 +18,4 @@ const usePostSignUp = () => { }); }; -export default usePostSignUp; \ No newline at end of file +export default usePostSignUp; diff --git a/src/apis/MyPage/api.ts b/src/apis/MyPage/api.ts index 8e1b7594..2e3cb6bd 100644 --- a/src/apis/MyPage/api.ts +++ b/src/apis/MyPage/api.ts @@ -1,5 +1,7 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; + import { UserRole } from "@/types/mentor"; import { BaseUserInfo } from "@/types/myInfo"; @@ -59,10 +61,10 @@ export const myPageApi = { return res.data; }, - patchInterestedRegionCountry: async (data: InterestedRegionCountryRequest): Promise => { - const res = await axiosInstance.patch( - `/my/interested-location`, data - ); + patchInterestedRegionCountry: async ( + data: InterestedRegionCountryRequest, + ): Promise => { + const res = await axiosInstance.patch(`/my/interested-location`, data); return res.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/MyPage/getProfile.ts b/src/apis/MyPage/getProfile.ts index cb38fa88..b3e47b09 100644 --- a/src/apis/MyPage/getProfile.ts +++ b/src/apis/MyPage/getProfile.ts @@ -1,9 +1,15 @@ import { AxiosError } from "axios"; -import { useMutationState, useQuery } from "@tanstack/react-query"; -import { myPageApi, MyInfoResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { MyInfoResponse, myPageApi } from "./api"; + +import { UseQueryResult, useMutationState, useQuery } from "@tanstack/react-query"; + +type UseGetMyInfoResult = Omit, "data"> & { + data: MyInfoResponse | undefined; +}; -const useGetMyInfo = () => { +const useGetMyInfo = (): UseGetMyInfoResult => { const queryResult = useQuery({ queryKey: [QueryKeys.MyPage.profile], queryFn: () => myPageApi.getProfile(), @@ -25,9 +31,9 @@ const useGetMyInfo = () => { const isOptimistic = pendingMutations.length > 0; const pendingData = isOptimistic ? pendingMutations[0] : null; - const displayData = isOptimistic ? { ...queryResult.data, ...pendingData } : queryResult.data; + const displayData = isOptimistic && queryResult.data ? { ...queryResult.data, ...pendingData } : queryResult.data; return { ...queryResult, data: displayData }; }; -export default useGetMyInfo; \ No newline at end of file +export default useGetMyInfo; diff --git a/src/apis/MyPage/index.ts b/src/apis/MyPage/index.ts index b52be717..d25fcb4d 100644 --- a/src/apis/MyPage/index.ts +++ b/src/apis/MyPage/index.ts @@ -1,5 +1,13 @@ -export { myPageApi, type MyInfoResponse, type MenteeInfo, type MentorInfo, type AdminInfo, type ProfilePatchRequest, type PasswordPatchRequest } from './api'; -export { default as useGetMyInfo } from './getProfile'; -export { default as usePatchMyInfo } from './patchProfile'; -export { default as usePatchMyPassword } from './patchPassword'; -export { default as usePatchInterestedRegionCountry } from './patchInterestedRegionCountry'; +export { + myPageApi, + type MyInfoResponse, + type MenteeInfo, + type MentorInfo, + type AdminInfo, + type ProfilePatchRequest, + type PasswordPatchRequest, +} from "./api"; +export { default as useGetMyInfo } from "./getProfile"; +export { default as usePatchMyInfo } from "./patchProfile"; +export { default as usePatchMyPassword } from "./patchPassword"; +export { default as usePatchInterestedRegionCountry } from "./patchInterestedRegionCountry"; diff --git a/src/apis/MyPage/patchInterestedRegionCountry.ts b/src/apis/MyPage/patchInterestedRegionCountry.ts index 3b03ccac..19fdef47 100644 --- a/src/apis/MyPage/patchInterestedRegionCountry.ts +++ b/src/apis/MyPage/patchInterestedRegionCountry.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { InterestedRegionCountryRequest, InterestedRegionCountryResponse, myPageApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { myPageApi, InterestedRegionCountryResponse, InterestedRegionCountryRequest } from "./api"; const usePatchInterestedRegionCountry = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePatchInterestedRegionCountry = () => { }); }; -export default usePatchInterestedRegionCountry; \ No newline at end of file +export default usePatchInterestedRegionCountry; diff --git a/src/apis/MyPage/patchPassword.ts b/src/apis/MyPage/patchPassword.ts index aed9834d..b9c1f96a 100644 --- a/src/apis/MyPage/patchPassword.ts +++ b/src/apis/MyPage/patchPassword.ts @@ -1,10 +1,13 @@ import { useRouter } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { myPageApi, PasswordPatchRequest } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { PasswordPatchRequest, myPageApi } from "./api"; + import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; const usePatchMyPassword = () => { const queryClient = useQueryClient(); @@ -27,4 +30,4 @@ const usePatchMyPassword = () => { }); }; -export default usePatchMyPassword; \ No newline at end of file +export default usePatchMyPassword; diff --git a/src/apis/MyPage/patchProfile.ts b/src/apis/MyPage/patchProfile.ts index cacbf78f..8e8bcb16 100644 --- a/src/apis/MyPage/patchProfile.ts +++ b/src/apis/MyPage/patchProfile.ts @@ -1,8 +1,10 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { myPageApi, ProfilePatchRequest } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { ProfilePatchRequest, myPageApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; const usePatchMyInfo = () => { const queryClient = useQueryClient(); @@ -25,4 +27,4 @@ const usePatchMyInfo = () => { }); }; -export default usePatchMyInfo; \ No newline at end of file +export default usePatchMyInfo; diff --git a/src/apis/Scores/api.ts b/src/apis/Scores/api.ts index 5b45182c..645e9a71 100644 --- a/src/apis/Scores/api.ts +++ b/src/apis/Scores/api.ts @@ -1,78 +1,79 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; +import { GpaScore, LanguageTestScore, LanguageTestEnum } from "@/types/score"; -export interface CreateLanguageTestResponse { - id: number; -} - -export type CreateLanguageTestRequest = Record; - -export interface LanguageTestListResponseLanguageTestScoreStatusResponseListItem { - id: number; - languageTestResponse: LanguageTestListResponseLanguageTestScoreStatusResponseListItemLanguageTestResponse; - verifyStatus: string; - rejectedReason: null; -} - -export interface LanguageTestListResponseLanguageTestScoreStatusResponseListItemLanguageTestResponse { - languageTestType: string; - languageTestScore: string; - languageTestReportUrl: string; -} +// ====== Query Keys ====== +export const ScoresQueryKeys = { + myGpaScore: "myGpaScore", + myLanguageTestScore: "myLanguageTestScore", +} as const; -export interface LanguageTestListResponse { - languageTestScoreStatusResponseList: LanguageTestListResponseLanguageTestScoreStatusResponseListItem[]; +// ====== Types ====== +export interface UseMyGpaScoreResponse { + gpaScoreStatusResponseList: GpaScore[]; } -export interface CreateGpaResponse { - id: number; +export interface UseGetMyLanguageTestScoreResponse { + languageTestScoreStatusResponseList: LanguageTestScore[]; } -export type CreateGpaRequest = Record; - -export interface GpaListResponseGpaScoreStatusResponseListItem { - id: number; - gpaResponse: GpaListResponseGpaScoreStatusResponseListItemGpaResponse; - verifyStatus: string; - rejectedReason: null; -} - -export interface GpaListResponseGpaScoreStatusResponseListItemGpaResponse { - gpa: number; - gpaCriteria: number; - gpaReportUrl: string; +export interface UsePostGpaScoreRequest { + gpaScoreRequest: { + gpa: number; + gpaCriteria: number; + issueDate: string; // yyyy-MM-dd + }; + file: Blob; } -export interface GpaListResponse { - gpaScoreStatusResponseList: GpaListResponseGpaScoreStatusResponseListItem[]; +export interface UsePostLanguageTestScoreRequest { + languageTestScoreRequest: { + languageTestType: LanguageTestEnum; + languageTestScore: string; + issueDate: string; // yyyy-MM-dd + }; + file: File; } +// ====== API Functions ====== export const scoresApi = { - postCreateLanguageTest: async (params: { data?: CreateLanguageTestRequest }): Promise => { - const res = await axiosInstance.post( - `/scores/language-tests`, params?.data - ); - return res.data; + /** + * 내 학점 점수 조회 + */ + getMyGpaScore: async (): Promise> => { + return axiosInstance.get("/scores/gpas"); }, - getLanguageTestList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/scores/language-tests`, { params: params?.params } - ); - return res.data; + /** + * 내 어학 점수 조회 + */ + getMyLanguageTestScore: async (): Promise> => { + return axiosInstance.get("/scores/language-tests"); }, - postCreateGpa: async (params: { data?: CreateGpaRequest }): Promise => { - const res = await axiosInstance.post( - `/scores/gpas`, params?.data + /** + * 학점 점수 제출 + */ + postGpaScore: async (request: UsePostGpaScoreRequest): Promise> => { + const formData = new FormData(); + formData.append( + "gpaScoreRequest", + new Blob([JSON.stringify(request.gpaScoreRequest)], { type: "application/json" }), ); - return res.data; + formData.append("file", request.file); + return axiosInstance.post("/scores/gpas", formData); }, - getGpaList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/scores/gpas`, { params: params?.params } + /** + * 어학 점수 제출 + */ + postLanguageTestScore: async (request: UsePostLanguageTestScoreRequest): Promise> => { + const formData = new FormData(); + formData.append( + "languageTestScoreRequest", + new Blob([JSON.stringify(request.languageTestScoreRequest)], { type: "application/json" }), ); - return res.data; + formData.append("file", request.file); + return axiosInstance.post("/scores/language-tests", formData); }, - }; \ No newline at end of file diff --git a/src/apis/Scores/getGpaList.ts b/src/apis/Scores/getGpaList.ts index b402a643..c0e06949 100644 --- a/src/apis/Scores/getGpaList.ts +++ b/src/apis/Scores/getGpaList.ts @@ -1,13 +1,18 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { scoresApi, GpaListResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { scoresApi, ScoresQueryKeys } from "./api"; +import { GpaScore } from "@/types/score"; -const useGetGpaList = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.Scores.gpaList, params], - queryFn: () => scoresApi.getGpaList(params ? { params } : {}), +/** + * @description 내 학점 점수 조회 훅 + */ +const useGetMyGpaScore = () => { + return useQuery({ + queryKey: [ScoresQueryKeys.myGpaScore], + queryFn: scoresApi.getMyGpaScore, + staleTime: Infinity, + select: (data) => data.data.gpaScoreStatusResponseList, }); }; -export default useGetGpaList; \ No newline at end of file +export default useGetMyGpaScore; \ No newline at end of file diff --git a/src/apis/Scores/getLanguageTestList.ts b/src/apis/Scores/getLanguageTestList.ts index ace30a13..d04c7d60 100644 --- a/src/apis/Scores/getLanguageTestList.ts +++ b/src/apis/Scores/getLanguageTestList.ts @@ -1,13 +1,18 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { scoresApi, LanguageTestListResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { scoresApi, ScoresQueryKeys } from "./api"; +import { LanguageTestScore } from "@/types/score"; -const useGetLanguageTestList = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.Scores.languageTestList, params], - queryFn: () => scoresApi.getLanguageTestList(params ? { params } : {}), +/** + * @description 내 어학 점수 조회 훅 + */ +const useGetMyLanguageTestScore = () => { + return useQuery({ + queryKey: [ScoresQueryKeys.myLanguageTestScore], + queryFn: scoresApi.getMyLanguageTestScore, + staleTime: Infinity, + select: (data) => data.data.languageTestScoreStatusResponseList, }); }; -export default useGetLanguageTestList; \ No newline at end of file +export default useGetMyLanguageTestScore; \ No newline at end of file diff --git a/src/apis/Scores/index.ts b/src/apis/Scores/index.ts index 73669f35..66124ea4 100644 --- a/src/apis/Scores/index.ts +++ b/src/apis/Scores/index.ts @@ -1,5 +1,12 @@ -export { scoresApi } from './api'; -export { default as getGpaList } from './getGpaList'; -export { default as getLanguageTestList } from './getLanguageTestList'; -export { default as postCreateGpa } from './postCreateGpa'; -export { default as postCreateLanguageTest } from './postCreateLanguageTest'; +export { scoresApi, ScoresQueryKeys } from './api'; +export type { + UseMyGpaScoreResponse, + UseGetMyLanguageTestScoreResponse, + UsePostGpaScoreRequest, + UsePostLanguageTestScoreRequest +} from './api'; + +export { default as useGetMyGpaScore } from './getGpaList'; +export { default as useGetMyLanguageTestScore } from './getLanguageTestList'; +export { default as usePostGpaScore } from './postCreateGpa'; +export { default as usePostLanguageTestScore } from './postCreateLanguageTest'; diff --git a/src/apis/Scores/postCreateGpa.ts b/src/apis/Scores/postCreateGpa.ts index 6255a5d0..bd692050 100644 --- a/src/apis/Scores/postCreateGpa.ts +++ b/src/apis/Scores/postCreateGpa.ts @@ -1,11 +1,27 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { scoresApi, CreateGpaResponse, CreateGpaRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { scoresApi, ScoresQueryKeys, UsePostGpaScoreRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostCreateGpa = () => { - return useMutation({ - mutationFn: (data) => scoresApi.postCreateGpa({ data }), +/** + * @description 학점 점수 제출 훅 + */ +export const usePostGpaScore = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (request: UsePostGpaScoreRequest) => scoresApi.postGpaScore(request), + + onSuccess: () => { + toast.success("학점 정보가 성공적으로 제출되었습니다."); + queryClient.invalidateQueries({ queryKey: [ScoresQueryKeys.myGpaScore] }); + }, + + onError: (error) => { + console.error("학점 제출 중 오류 발생:", error); + toast.error("오류가 발생했습니다. 다시 시도해주세요."); + }, }); }; -export default usePostCreateGpa; \ No newline at end of file +export default usePostGpaScore; \ No newline at end of file diff --git a/src/apis/Scores/postCreateLanguageTest.ts b/src/apis/Scores/postCreateLanguageTest.ts index b6201433..d3917dec 100644 --- a/src/apis/Scores/postCreateLanguageTest.ts +++ b/src/apis/Scores/postCreateLanguageTest.ts @@ -1,11 +1,27 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { scoresApi, CreateLanguageTestResponse, CreateLanguageTestRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { scoresApi, ScoresQueryKeys, UsePostLanguageTestScoreRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostCreateLanguageTest = () => { - return useMutation({ - mutationFn: (data) => scoresApi.postCreateLanguageTest({ data }), +/** + * @description 어학 점수 제출 훅 + */ +export const usePostLanguageTestScore = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (request: UsePostLanguageTestScoreRequest) => scoresApi.postLanguageTestScore(request), + + onSuccess: () => { + toast.success("어학 성적이 성공적으로 제출되었습니다."); + queryClient.invalidateQueries({ queryKey: [ScoresQueryKeys.myLanguageTestScore] }); + }, + + onError: (error) => { + console.error("어학 성적 제출 중 오류 발생:", error); + toast.error("오류가 발생했습니다. 다시 시도해주세요."); + }, }); }; -export default usePostCreateLanguageTest; \ No newline at end of file +export default usePostLanguageTestScore; \ No newline at end of file diff --git a/src/apis/applications/api.ts b/src/apis/applications/api.ts index f4879dd1..2f61ddf5 100644 --- a/src/apis/applications/api.ts +++ b/src/apis/applications/api.ts @@ -1,147 +1,42 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; +import { ApplicationListResponse } from "@/types/application"; -export interface CompetitorsResponseThirdChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: CompetitorsResponseThirdChoiceItemApplicantsItem[]; -} - -export interface CompetitorsResponseThirdChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface CompetitorsResponseSecondChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: CompetitorsResponseSecondChoiceItemApplicantsItem[]; -} - -export interface CompetitorsResponseSecondChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface CompetitorsResponseFirstChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: CompetitorsResponseFirstChoiceItemApplicantsItem[]; -} - -export interface CompetitorsResponseFirstChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface CompetitorsResponse { - firstChoice: CompetitorsResponseFirstChoiceItem[]; - secondChoice: CompetitorsResponseSecondChoiceItem[]; - thirdChoice: CompetitorsResponseThirdChoiceItem[]; -} - -export interface SubmitApplicationResponseAppliedUniversities { - firstChoiceUniversity: string; - secondChoiceUniversity: string; - thirdChoiceUniversity: string; -} +// ====== Query Keys ====== +export const ApplicationsQueryKeys = { + competitorsApplicationList: "competitorsApplicationList", +} as const; -export interface SubmitApplicationResponse { - totalApplyCount: number; - applyCount: number; - appliedUniversities: SubmitApplicationResponseAppliedUniversities; +// ====== Types ====== +export interface UseSubmitApplicationResponse { + isSuccess: boolean; } -export type SubmitApplicationRequest = Record; - -export interface ApplicantsResponseThirdChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: ApplicantsResponseThirdChoiceItemApplicantsItem[]; -} - -export interface ApplicantsResponseThirdChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface ApplicantsResponseSecondChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: ApplicantsResponseSecondChoiceItemApplicantsItem[]; -} - -export interface ApplicantsResponseSecondChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface ApplicantsResponseFirstChoiceItem { - koreanName: string; - studentCapacity: number; - region: string; - country: string; - applicants: ApplicantsResponseFirstChoiceItemApplicantsItem[]; -} - -export interface ApplicantsResponseFirstChoiceItemApplicantsItem { - nicknameForApply: string; - gpa: number; - testType: string; - testScore: string; - isMine: boolean; -} - -export interface ApplicantsResponse { - firstChoice: ApplicantsResponseFirstChoiceItem[]; - secondChoice: ApplicantsResponseSecondChoiceItem[]; - thirdChoice: ApplicantsResponseThirdChoiceItem[]; +export interface UseSubmitApplicationRequest { + gpaScoreId: number; + languageTestScoreId: number; + universityChoiceRequest: { + firstChoiceUniversityId: number | null; + secondChoiceUniversityId: number | null; + thirdChoiceUniversityId: number | null; + }; } +// ====== API Functions ====== export const applicationsApi = { - getCompetitors: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/applications/competitors`, { params: params?.params } - ); - return res.data; + /** + * 지원 목록 조회 + */ + getApplicationsList: async (): Promise> => { + return axiosInstance.get("/applications"); }, - postSubmitApplication: async (params: { data?: SubmitApplicationRequest }): Promise => { - const res = await axiosInstance.post( - `/applications`, params?.data - ); - return res.data; + /** + * 지원 제출 + */ + postSubmitApplication: async ( + request: UseSubmitApplicationRequest, + ): Promise> => { + return axiosInstance.post("/applications", request); }, - - getApplicants: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/applications`, { params: params?.params } - ); - return res.data; - }, - }; \ No newline at end of file diff --git a/src/apis/applications/getApplicants.ts b/src/apis/applications/getApplicants.ts index 2a266ec3..5b24eae0 100644 --- a/src/apis/applications/getApplicants.ts +++ b/src/apis/applications/getApplicants.ts @@ -1,13 +1,30 @@ -import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { applicationsApi, ApplicantsResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { AxiosError, AxiosResponse } from "axios"; +import { UseQueryOptions, UseQueryResult, useQuery } from "@tanstack/react-query"; +import { applicationsApi, ApplicationsQueryKeys } from "./api"; +import { ApplicationListResponse } from "@/types/application"; -const useGetApplicants = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.applications.applicants, params], - queryFn: () => applicationsApi.getApplicants(params ? { params } : {}), +type UseGetApplicationsListOptions = Omit< + UseQueryOptions< + AxiosResponse, + AxiosError<{ message: string }>, + ApplicationListResponse + >, + "queryKey" | "queryFn" +>; + +/** + * @description 지원 목록 조회 훅 + */ +const useGetApplicationsList = ( + props?: UseGetApplicationsListOptions, +): UseQueryResult> => { + return useQuery({ + queryKey: [ApplicationsQueryKeys.competitorsApplicationList], + queryFn: applicationsApi.getApplicationsList, + staleTime: 1000 * 60 * 5, // 5분간 캐시 + select: (response) => response.data, + ...props, }); }; -export default useGetApplicants; \ No newline at end of file +export default useGetApplicationsList; \ No newline at end of file diff --git a/src/apis/applications/index.ts b/src/apis/applications/index.ts index 3169f9b1..a70a656f 100644 --- a/src/apis/applications/index.ts +++ b/src/apis/applications/index.ts @@ -1,4 +1,5 @@ -export { applicationsApi } from './api'; -export { default as getApplicants } from './getApplicants'; -export { default as getCompetitors } from './getCompetitors'; -export { default as postSubmitApplication } from './postSubmitApplication'; +export { applicationsApi, ApplicationsQueryKeys } from './api'; +export type { UseSubmitApplicationResponse, UseSubmitApplicationRequest } from './api'; +export { default as useGetApplicationsList } from './getApplicants'; +export { default as useGetCompetitors } from './getCompetitors'; +export { default as usePostSubmitApplication } from './postSubmitApplication'; diff --git a/src/apis/applications/postSubmitApplication.ts b/src/apis/applications/postSubmitApplication.ts index 5434d6ee..a5ccd125 100644 --- a/src/apis/applications/postSubmitApplication.ts +++ b/src/apis/applications/postSubmitApplication.ts @@ -1,10 +1,35 @@ -import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { applicationsApi, SubmitApplicationResponse, SubmitApplicationRequest } from "./api"; +import { AxiosError, AxiosResponse } from "axios"; +import { UseMutationOptions, UseMutationResult, useMutation } from "@tanstack/react-query"; +import { applicationsApi, UseSubmitApplicationResponse, UseSubmitApplicationRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostSubmitApplication = () => { - return useMutation({ - mutationFn: (data) => applicationsApi.postSubmitApplication({ data }), +/** + * @description 지원 제출 훅 + */ +const usePostSubmitApplication = ( + props?: UseMutationOptions< + AxiosResponse, + AxiosError<{ message: string }>, + UseSubmitApplicationRequest, + unknown + >, +): UseMutationResult< + AxiosResponse, + AxiosError<{ message: string }>, + UseSubmitApplicationRequest, + unknown +> => { + return useMutation< + AxiosResponse, + AxiosError<{ message: string }>, + UseSubmitApplicationRequest + >({ + ...props, + mutationFn: applicationsApi.postSubmitApplication, + onError: (error) => { + const errorMessage = error?.response?.data?.message; + toast.error(errorMessage || "지원 중 오류가 발생했습니다. 다시 시도해주세요."); + }, }); }; diff --git a/src/apis/chat/api.ts b/src/apis/chat/api.ts index 3041ff33..df2d984b 100644 --- a/src/apis/chat/api.ts +++ b/src/apis/chat/api.ts @@ -1,65 +1,57 @@ +import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; -export interface ChatMessagesResponseContentItem { - id: number; - content: string; - senderId: number; - createdAt: string; - attachments: ChatMessagesResponseContentItemAttachmentsItem[]; -} +import { ChatMessage, ChatPartner, ChatRoom } from "@/types/chat"; -export interface ChatMessagesResponseContentItemAttachmentsItem { - id: number; - isImage: boolean; - url: string; - thumbnailUrl: string | null; - createdAt: string; -} - -export interface ChatMessagesResponse { - nextPageNumber: number; - content: ChatMessagesResponseContentItem[]; -} +// QueryKeys for chat domain +export const ChatQueryKeys = { + chatRooms: "chatRooms", + chatHistories: "chatHistories", + partnerInfo: "partnerInfo", +} as const; -export type ChatRoomsResponse = void; +// Re-export types from @/types/chat +export type { ChatMessage, ChatRoom, ChatPartner }; -export type ReadChatRoomResponse = void; +export interface ChatHistoriesResponse { + nextPageNumber: number; // 다음 페이지가 없다면 -1 + content: ChatMessage[]; +} -export type ReadChatRoomRequest = Record; +export interface ChatRoomListResponse { + chatRooms: ChatRoom[]; +} -export interface ChatPartnerResponse { - partnerId: number; - nickname: string; - profileUrl: string; +interface GetChatHistoriesParams { + roomId: number; + size?: number; + page?: number; } export const chatApi = { - getChatMessages: async (params: { roomId: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/chats/rooms/${params.roomId}?size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } - ); + getChatHistories: async ({ roomId, size = 20, page = 0 }: GetChatHistoriesParams): Promise => { + const res = await axiosInstance.get(`/chats/rooms/${roomId}`, { + params: { + size, + page, + }, + }); return res.data; }, - getChatRooms: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/chats/rooms`, { params: params?.params } - ); + getChatRooms: async (): Promise => { + const res = await axiosInstance.get("/chats/rooms"); return res.data; }, - putReadChatRoom: async (params: { roomId: string | number, data?: ReadChatRoomRequest }): Promise => { - const res = await axiosInstance.put( - `//chats/rooms/${params.roomId}/read`, params?.data - ); - return res.data; + putReadChatRoom: async (roomId: number): Promise => { + const response: AxiosResponse = await axiosInstance.put(`/chats/rooms/${roomId}/read`); + return response.data; }, - getChatPartner: async (params: { roomId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/chats/rooms/${params.roomId}/partner`, { params: params?.params } - ); + getChatPartner: async (roomId: number): Promise => { + const res = await axiosInstance.get(`/chats/rooms/${roomId}/partner`); return res.data; }, - -}; \ No newline at end of file +}; diff --git a/src/apis/chat/getChatMessages.ts b/src/apis/chat/getChatMessages.ts index 2cf05b68..9c0adf12 100644 --- a/src/apis/chat/getChatMessages.ts +++ b/src/apis/chat/getChatMessages.ts @@ -1,14 +1,42 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { chatApi, ChatMessagesResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; - -const useGetChatMessages = (roomId: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.chat.chatMessages, roomId, defaultSize, defaultPage, params], - queryFn: () => chatApi.getChatMessages({ roomId, defaultSize, defaultPage, params }), - enabled: !!roomId && !!defaultSize && !!defaultPage, + +import { ChatHistoriesResponse, ChatMessage, ChatQueryKeys, chatApi } from "./api"; + +import { useInfiniteQuery } from "@tanstack/react-query"; + +/** + * @description 채팅 히스토리를 무한 스크롤로 가져오는 훅 + */ +const useGetChatHistories = (roomId: number, size: number = 20) => { + return useInfiniteQuery< + ChatHistoriesResponse, + AxiosError, + { + pages: ChatHistoriesResponse[]; + pageParams: number[]; + messages: ChatMessage[]; + }, + [string, number], + number + >({ + queryKey: [ChatQueryKeys.chatHistories, roomId], + queryFn: ({ pageParam = 0 }: { pageParam?: number }) => chatApi.getChatHistories({ roomId, size, page: pageParam }), + initialPageParam: 0, + getNextPageParam: (lastPage: ChatHistoriesResponse) => { + // nextPageNumber가 -1이면 더 이상 페이지가 없음 + return lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber; + }, + staleTime: 1000 * 60 * 5, // 5분간 캐시 + enabled: !!roomId, // roomId가 있을 때만 쿼리 실행 + meta: { + disableGlobalLoading: true, // 전역 로딩 비활성화 + }, + select: (data) => ({ + pages: data.pages, + pageParams: data.pageParams, + messages: data.pages.flatMap((page) => page.content), + }), }); }; -export default useGetChatMessages; \ No newline at end of file +export default useGetChatHistories; diff --git a/src/apis/chat/getChatPartner.ts b/src/apis/chat/getChatPartner.ts index ab3ab0c0..b6143abc 100644 --- a/src/apis/chat/getChatPartner.ts +++ b/src/apis/chat/getChatPartner.ts @@ -1,14 +1,17 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { chatApi, ChatPartnerResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { chatApi, ChatQueryKeys, ChatPartner } from "./api"; -const useGetChatPartner = (roomId: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.chat.chatPartner, roomId, params], - queryFn: () => chatApi.getChatPartner({ roomId, params }), +/** + * @description 채팅 상대방 정보를 가져오는 훅 + */ +const useGetPartnerInfo = (roomId: number) => { + return useQuery({ + queryKey: [ChatQueryKeys.partnerInfo, roomId], + queryFn: () => chatApi.getChatPartner(roomId), + staleTime: 1000 * 60 * 5, enabled: !!roomId, }); }; -export default useGetChatPartner; \ No newline at end of file +export default useGetPartnerInfo; \ No newline at end of file diff --git a/src/apis/chat/getChatRooms.ts b/src/apis/chat/getChatRooms.ts index b2c6c0c4..362583c4 100644 --- a/src/apis/chat/getChatRooms.ts +++ b/src/apis/chat/getChatRooms.ts @@ -1,13 +1,19 @@ import { AxiosError } from "axios"; + +import { ChatQueryKeys, ChatRoom, ChatRoomListResponse, chatApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { chatApi, ChatRoomsResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; -const useGetChatRooms = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.chat.chatRooms, params], - queryFn: () => chatApi.getChatRooms(params ? { params } : {}), +/** + * @description 채팅방 목록을 가져오는 훅 + */ +const useGetChatRooms = () => { + return useQuery({ + queryKey: [ChatQueryKeys.chatRooms], + queryFn: chatApi.getChatRooms, + staleTime: 1000 * 60 * 5, // 5분간 캐시 + select: (data) => data.chatRooms, }); }; -export default useGetChatRooms; \ No newline at end of file +export default useGetChatRooms; diff --git a/src/apis/chat/index.ts b/src/apis/chat/index.ts index c6566109..6519352b 100644 --- a/src/apis/chat/index.ts +++ b/src/apis/chat/index.ts @@ -1,5 +1,6 @@ -export { chatApi } from './api'; -export { default as getChatMessages } from './getChatMessages'; -export { default as getChatPartner } from './getChatPartner'; -export { default as getChatRooms } from './getChatRooms'; -export { default as putReadChatRoom } from './putReadChatRoom'; +export { chatApi, ChatQueryKeys } from './api'; +export type { ChatHistoriesResponse, ChatRoomListResponse, ChatMessage, ChatRoom, ChatPartner } from './api'; +export { default as useGetChatHistories } from './getChatMessages'; +export { default as useGetPartnerInfo } from './getChatPartner'; +export { default as useGetChatRooms } from './getChatRooms'; +export { default as usePutChatRead } from './putReadChatRoom'; diff --git a/src/apis/chat/putReadChatRoom.ts b/src/apis/chat/putReadChatRoom.ts index 02af78df..b72805da 100644 --- a/src/apis/chat/putReadChatRoom.ts +++ b/src/apis/chat/putReadChatRoom.ts @@ -1,11 +1,23 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { chatApi, ReadChatRoomResponse, ReadChatRoomRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { chatApi, ChatQueryKeys } from "./api"; -const usePutReadChatRoom = () => { - return useMutation({ - mutationFn: (variables) => chatApi.putReadChatRoom(variables), +/** + * @description 채팅방 읽음 처리 훅 + */ +const usePutChatRead = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: chatApi.putReadChatRoom, + onSuccess: () => { + // 채팅방 목록 쿼리를 무효화하여 새로 고침 + queryClient.invalidateQueries({ queryKey: [ChatQueryKeys.chatRooms] }); + }, + onError: (error) => { + console.error("채팅방 진입 읽기 실패", error); + }, }); }; -export default usePutReadChatRoom; \ No newline at end of file +export default usePutChatRead; \ No newline at end of file diff --git a/src/apis/community/api.ts b/src/apis/community/api.ts index 8ec61bc2..ef780017 100644 --- a/src/apis/community/api.ts +++ b/src/apis/community/api.ts @@ -1,18 +1,22 @@ import { AxiosResponse } from "axios"; -import { axiosInstance } from "@/utils/axiosInstance"; -import { - Post, - PostCreateRequest, - PostIdResponse, - PostUpdateRequest, - PostLikeResponse, + +import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; + +import { CommentCreateRequest, - CommentIdResponse + CommentIdResponse, + ListPost, + Post, + PostCreateRequest, + PostIdResponse, + PostLikeResponse, + PostUpdateRequest, } from "@/types/community"; // QueryKeys for community domain export const CommunityQueryKeys = { posts: "posts", + postList: "postList1", // 기존 api/boards와 동일한 키 유지 } as const; export interface BoardListResponse { @@ -48,28 +52,33 @@ export interface DeletePostResponse { } // Re-export types from @/types/community for convenience -export type { - Post, - PostCreateRequest, - PostIdResponse, - PostUpdateRequest, +export type { + Post, + PostCreateRequest, + PostIdResponse, + PostUpdateRequest, PostLikeResponse, CommentCreateRequest, - CommentIdResponse + CommentIdResponse, + ListPost, }; export const communityApi = { + /** + * 게시글 목록 조회 (클라이언트) + */ + getPostList: (boardCode: string, category: string | null = null): Promise> => { + const params = category && category !== "전체" ? { category } : {}; + return publicAxiosInstance.get(`/boards/${boardCode}`, { params }); + }, + getBoardList: async (params?: Record): Promise => { - const res = await axiosInstance.get( - `/boards`, { params } - ); + const res = await axiosInstance.get(`/boards`, { params }); return res.data; }, getBoard: async (boardCode: string, params?: Record): Promise => { - const res = await axiosInstance.get( - `/boards/${boardCode}`, { params } - ); + const res = await axiosInstance.get(`/boards/${boardCode}`, { params }); return res.data; }, @@ -82,7 +91,7 @@ export const communityApi = { const convertedRequest: FormData = new FormData(); convertedRequest.append( "postCreateRequest", - new Blob([JSON.stringify(request.postCreateRequest)], { type: "application/json" }) + new Blob([JSON.stringify(request.postCreateRequest)], { type: "application/json" }), ); request.file.forEach((file) => { convertedRequest.append("file", file); @@ -91,7 +100,7 @@ export const communityApi = { const response: AxiosResponse = await axiosInstance.post(`/posts`, convertedRequest, { headers: { "Content-Type": "multipart/form-data" }, }); - + return { ...response.data, boardCode: request.postCreateRequest.boardCode, @@ -139,9 +148,7 @@ export const communityApi = { }, updateComment: async (commentId: number, data: { content: string }): Promise => { - const res = await axiosInstance.patch( - `/comments/${commentId}`, data - ); + const res = await axiosInstance.patch(`/comments/${commentId}`, data); return res.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/community/deleteComment.ts b/src/apis/community/deleteComment.ts index 0741e537..25bba468 100644 --- a/src/apis/community/deleteComment.ts +++ b/src/apis/community/deleteComment.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, CommentIdResponse } from "./api"; + +import { CommentIdResponse, CommunityQueryKeys, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; interface DeleteCommentRequest { commentId: number; @@ -28,4 +30,4 @@ const useDeleteComment = () => { }); }; -export default useDeleteComment; \ No newline at end of file +export default useDeleteComment; diff --git a/src/apis/community/deleteLikePost.ts b/src/apis/community/deleteLikePost.ts index 1dac62ef..295fb6da 100644 --- a/src/apis/community/deleteLikePost.ts +++ b/src/apis/community/deleteLikePost.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, PostLikeResponse } from "./api"; + +import { CommunityQueryKeys, PostLikeResponse, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 게시글 좋아요 취소를 위한 useMutation 커스텀 훅 @@ -22,4 +24,4 @@ const useDeleteLike = () => { }); }; -export default useDeleteLike; \ No newline at end of file +export default useDeleteLike; diff --git a/src/apis/community/deletePost.ts b/src/apis/community/deletePost.ts index 7290d4e9..c1883dd7 100644 --- a/src/apis/community/deletePost.ts +++ b/src/apis/community/deletePost.ts @@ -1,8 +1,11 @@ import { useRouter } from "next/navigation"; -import { AxiosResponse, AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, DeletePostResponse } from "./api"; + +import { AxiosError, AxiosResponse } from "axios"; + +import { CommunityQueryKeys, DeletePostResponse, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 게시글 삭제를 위한 useMutation 커스텀 훅 @@ -30,4 +33,4 @@ const useDeletePost = () => { }); }; -export default useDeletePost; \ No newline at end of file +export default useDeletePost; diff --git a/src/apis/community/getPostDetail.ts b/src/apis/community/getPostDetail.ts index 3a71fda8..034c33af 100644 --- a/src/apis/community/getPostDetail.ts +++ b/src/apis/community/getPostDetail.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { CommunityQueryKeys, Post, communityApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, Post } from "./api"; /** * @description 게시글 상세 조회를 위한 useQuery 커스텀 훅 @@ -13,4 +15,4 @@ const useGetPostDetail = (postId: number) => { }); }; -export default useGetPostDetail; \ No newline at end of file +export default useGetPostDetail; diff --git a/src/apis/community/getPostList.ts b/src/apis/community/getPostList.ts new file mode 100644 index 00000000..3715ae14 --- /dev/null +++ b/src/apis/community/getPostList.ts @@ -0,0 +1,28 @@ +import { AxiosResponse } from "axios"; +import { useQuery } from "@tanstack/react-query"; +import { communityApi, CommunityQueryKeys } from "./api"; +import { ListPost } from "@/types/community"; + +interface UseGetPostListProps { + boardCode: string; + category?: string | null; +} + +/** + * @description 게시글 목록 조회 훅 + */ +const useGetPostList = ({ boardCode, category = null }: UseGetPostListProps) => { + return useQuery({ + queryKey: [CommunityQueryKeys.postList, boardCode, category], + queryFn: () => communityApi.getPostList(boardCode, category), + staleTime: Infinity, + gcTime: 1000 * 60 * 30, // 30분 + select: (response) => { + return [...response.data].sort((a, b) => { + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }); + }, + }); +}; + +export default useGetPostList; diff --git a/src/apis/community/index.ts b/src/apis/community/index.ts index 4d95a071..f847cdc8 100644 --- a/src/apis/community/index.ts +++ b/src/apis/community/index.ts @@ -1,12 +1,26 @@ -export { communityApi } from './api'; -export { default as deleteComment } from './deleteComment'; -export { default as deleteLikePost } from './deleteLikePost'; -export { default as deletePost } from './deletePost'; -export { default as getBoard } from './getBoard'; -export { default as getBoardList } from './getBoardList'; -export { default as getPostDetail } from './getPostDetail'; -export { default as patchUpdateComment } from './patchUpdateComment'; -export { default as patchUpdatePost } from './patchUpdatePost'; -export { default as postCreateComment } from './postCreateComment'; -export { default as postCreatePost } from './postCreatePost'; -export { default as postLikePost } from './postLikePost'; +export { communityApi, CommunityQueryKeys } from "./api"; +export type { + Post, + PostCreateRequest, + PostIdResponse, + PostUpdateRequest, + PostLikeResponse, + CommentCreateRequest, + CommentIdResponse, + ListPost, +} from "./api"; +export { default as useDeleteComment } from "./deleteComment"; +export { default as useDeleteLike } from "./deleteLikePost"; +export { default as useDeletePost } from "./deletePost"; +export { default as useGetBoard } from "./getBoard"; +export { default as useGetBoardList } from "./getBoardList"; +export { default as useGetPostDetail } from "./getPostDetail"; +export { default as useGetPostList } from "./getPostList"; +export { default as usePatchUpdateComment } from "./patchUpdateComment"; +export { default as useUpdatePost } from "./patchUpdatePost"; +export { default as useCreateComment } from "./postCreateComment"; +export { default as useCreatePost } from "./postCreatePost"; +export { default as usePostLike } from "./postLikePost"; + +// Server-side functions +export { getPostListServer } from "./server"; diff --git a/src/apis/community/patchUpdatePost.ts b/src/apis/community/patchUpdatePost.ts index e36566b2..8923406c 100644 --- a/src/apis/community/patchUpdatePost.ts +++ b/src/apis/community/patchUpdatePost.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, PostIdResponse, PostUpdateRequest } from "./api"; + +import { CommunityQueryKeys, PostIdResponse, PostUpdateRequest, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; interface UpdatePostVariables { postId: number; @@ -29,4 +31,4 @@ const useUpdatePost = () => { }); }; -export default useUpdatePost; \ No newline at end of file +export default useUpdatePost; diff --git a/src/apis/community/postCreateComment.ts b/src/apis/community/postCreateComment.ts index a27ccfc5..97a28f49 100644 --- a/src/apis/community/postCreateComment.ts +++ b/src/apis/community/postCreateComment.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, CommentCreateRequest, CommentIdResponse } from "./api"; + +import { CommentCreateRequest, CommentIdResponse, CommunityQueryKeys, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 댓글 생성을 위한 useMutation 커스텀 훅 @@ -23,4 +25,4 @@ const useCreateComment = () => { }); }; -export default useCreateComment; \ No newline at end of file +export default useCreateComment; diff --git a/src/apis/community/postCreatePost.ts b/src/apis/community/postCreatePost.ts index ea9e3bfb..446bd9ee 100644 --- a/src/apis/community/postCreatePost.ts +++ b/src/apis/community/postCreatePost.ts @@ -1,8 +1,10 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, PostCreateRequest, PostIdResponse } from "./api"; + +import { CommunityQueryKeys, PostCreateRequest, PostIdResponse, communityApi } from "./api"; + import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description ISR 페이지를 revalidate하는 함수 @@ -41,12 +43,12 @@ const useCreatePost = () => { onSuccess: async (data) => { // 게시글 목록 쿼리를 무효화하여 최신 목록 반영 queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] }); - + // ISR 페이지 revalidate (사용자 인증 토큰 사용) if (accessToken) { await revalidateCommunityPage(data.boardCode, accessToken); } - + toast.success("게시글이 등록되었습니다."); }, onError: (error) => { @@ -56,4 +58,4 @@ const useCreatePost = () => { }); }; -export default useCreatePost; \ No newline at end of file +export default useCreatePost; diff --git a/src/apis/community/postLikePost.ts b/src/apis/community/postLikePost.ts index 2e95b3e9..2591446d 100644 --- a/src/apis/community/postLikePost.ts +++ b/src/apis/community/postLikePost.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys, PostLikeResponse } from "./api"; + +import { CommunityQueryKeys, PostLikeResponse, communityApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 게시글 좋아요를 위한 useMutation 커스텀 훅 @@ -22,4 +24,4 @@ const usePostLike = () => { }); }; -export default usePostLike; \ No newline at end of file +export default usePostLike; diff --git a/src/api/boards/server/getPostList.ts b/src/apis/community/server.ts similarity index 71% rename from src/api/boards/server/getPostList.ts rename to src/apis/community/server.ts index d7b7dfba..e311c887 100644 --- a/src/api/boards/server/getPostList.ts +++ b/src/apis/community/server.ts @@ -1,5 +1,4 @@ import serverFetch, { ServerFetchResult } from "@/utils/serverFetchUtil"; - import { ListPost } from "@/types/community"; interface GetPostListParams { @@ -15,13 +14,12 @@ interface GetPostListParams { * @param revalidate - ISR revalidate 시간(초) 또는 false (무한 캐시) * @returns Promise> */ -export const getPostList = async ({ +export const getPostListServer = async ({ boardCode, category = null, - revalidate = false, // 기본값: 자동 재생성 비활성화 (수동 revalidate만) + revalidate = false, }: GetPostListParams): Promise> => { const params = new URLSearchParams(); - // "전체"는 필터 없음을 의미하므로 파라미터에 포함하지 않음 if (category && category !== "전체") { params.append("category", category); } @@ -32,8 +30,8 @@ export const getPostList = async ({ return serverFetch(url, { method: "GET", next: { - revalidate: revalidate === false ? undefined : revalidate, - tags: [`posts-${boardCode}`], // 태그 기반 revalidation 지원 (글 작성 시만 revalidate) + revalidate, + tags: [`posts-${boardCode}`], }, }); }; diff --git a/src/apis/image-upload/api.ts b/src/apis/image-upload/api.ts index a2024dda..8a694299 100644 --- a/src/apis/image-upload/api.ts +++ b/src/apis/image-upload/api.ts @@ -1,34 +1,28 @@ -import { axiosInstance } from "@/utils/axiosInstance"; +import { AxiosResponse } from "axios"; +import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; +import { FileResponse } from "@/types/file"; +// ====== Types ====== export type SlackNotificationResponse = void; - export type SlackNotificationRequest = Record; export interface UploadLanguageTestReportResponse { fileUrl: string; } -export type UploadLanguageTestReportRequest = Record; - export interface UploadProfileImageResponse { fileUrl: string; } -export type UploadProfileImageRequest = Record; - -export interface UploadProfileImageBeforeSignupResponse { - fileUrl: string; -} - -export type UploadProfileImageBeforeSignupRequest = Record; - export interface UploadGpaReportResponse { fileUrl: string; } -export type UploadGpaReportRequest = Record; - +// ====== API Functions ====== export const imageUploadApi = { + /** + * 슬랙 알림 전송 + */ postSlackNotification: async (params: { data?: SlackNotificationRequest }): Promise => { const res = await axiosInstance.post( `https://hooks.slack.com/services/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`, params?.data @@ -36,32 +30,51 @@ export const imageUploadApi = { return res.data; }, - postUploadLanguageTestReport: async (params: { data?: UploadLanguageTestReportRequest }): Promise => { + /** + * 어학 성적 증명서 업로드 + */ + postUploadLanguageTestReport: async (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); const res = await axiosInstance.post( - `/file/language-test`, params?.data + `/file/language-test`, formData, { headers: { "Content-Type": "multipart/form-data" } } ); return res.data; }, - postUploadProfileImage: async (params: { data?: UploadProfileImageRequest }): Promise => { + /** + * 프로필 이미지 업로드 (로그인 후) + */ + postUploadProfileImage: async (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); const res = await axiosInstance.post( - `/file/profile/post`, params?.data + `/file/profile/post`, formData, { headers: { "Content-Type": "multipart/form-data" } } ); return res.data; }, - postUploadProfileImageBeforeSignup: async (params: { data?: UploadProfileImageBeforeSignupRequest }): Promise => { - const res = await axiosInstance.post( - `/file/profile/pre`, params?.data + /** + * 프로필 이미지 업로드 (회원가입 전, 공개 API) + */ + postUploadProfileImageBeforeSignup: async (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); + const response: AxiosResponse = await publicAxiosInstance.post( + "/file/profile/pre", formData, { headers: { "Content-Type": "multipart/form-data" } } ); - return res.data; + return response.data; }, - postUploadGpaReport: async (params: { data?: UploadGpaReportRequest }): Promise => { + /** + * 학점 증명서 업로드 + */ + postUploadGpaReport: async (file: File): Promise => { + const formData = new FormData(); + formData.append("file", file); const res = await axiosInstance.post( - `/file/gpa`, params?.data + `/file/gpa`, formData, { headers: { "Content-Type": "multipart/form-data" } } ); return res.data; }, - }; \ No newline at end of file diff --git a/src/apis/image-upload/index.ts b/src/apis/image-upload/index.ts index 95f3e6f9..9a7dad88 100644 --- a/src/apis/image-upload/index.ts +++ b/src/apis/image-upload/index.ts @@ -1,6 +1,12 @@ export { imageUploadApi } from './api'; -export { default as postSlackNotification } from './postSlackNotification'; -export { default as postUploadGpaReport } from './postUploadGpaReport'; -export { default as postUploadLanguageTestReport } from './postUploadLanguageTestReport'; -export { default as postUploadProfileImage } from './postUploadProfileImage'; -export { default as postUploadProfileImageBeforeSignup } from './postUploadProfileImageBeforeSignup'; +export type { + UploadLanguageTestReportResponse, + UploadProfileImageResponse, + UploadGpaReportResponse +} from './api'; + +export { default as useSlackNotification } from './postSlackNotification'; +export { default as useUploadGpaReport } from './postUploadGpaReport'; +export { default as useUploadLanguageTestReport } from './postUploadLanguageTestReport'; +export { default as useUploadProfileImage } from './postUploadProfileImage'; +export { default as useUploadProfileImagePublic } from './postUploadProfileImageBeforeSignup'; diff --git a/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts index edf2b95c..d2ddb6d5 100644 --- a/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts +++ b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts @@ -1,11 +1,20 @@ import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { imageUploadApi, UploadProfileImageBeforeSignupResponse, UploadProfileImageBeforeSignupRequest } from "./api"; +import { imageUploadApi } from "./api"; +import { FileResponse } from "@/types/file"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostUploadProfileImageBeforeSignup = () => { - return useMutation({ - mutationFn: (data) => imageUploadApi.postUploadProfileImageBeforeSignup({ data }), +/** + * @description 프로필 이미지 업로드를 위한 useMutation 커스텀 훅 (회원가입 전 공개 API) + */ +const useUploadProfileImagePublic = () => { + return useMutation({ + mutationFn: imageUploadApi.postUploadProfileImageBeforeSignup, + onError: (error) => { + console.error("프로필 이미지 업로드 실패:", error); + toast.error("이미지 업로드에 실패했습니다."); + }, }); }; -export default usePostUploadProfileImageBeforeSignup; \ No newline at end of file +export default useUploadProfileImagePublic; \ No newline at end of file diff --git a/src/apis/mentor/api.ts b/src/apis/mentor/api.ts index 1a11d710..e2ce44a8 100644 --- a/src/apis/mentor/api.ts +++ b/src/apis/mentor/api.ts @@ -1,230 +1,174 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; - -export interface MatchedMentorsResponseContentItem { - id: number; - roomId: number; - nickname: string; - profileImageUrl: string; - country: string; - universityName: string; - term: string; - menteeCount: number; - hasBadge: boolean; - introduction: string; - channels: MatchedMentorsResponseContentItemChannelsItem[]; - isApplied: boolean; +import { MentorCardPreview, MentorCardDetail, MentoringItem, MentoringApprovalStatus } from "@/types/mentor"; +import { MentoringListItem, VerifyStatus } from "@/types/mentee"; + +// QueryKeys for mentor domain +export const MentorQueryKeys = { + myMentorProfile: "myMentorProfile", + mentoringList: "mentoringList", + mentoringNewCount: "mentoringNewCount", + applyMentoringList: "applyMentoringList", + mentorList: "mentorList", + mentorDetail: "mentorDetail", +} as const; + +// Re-export types +export type { MentorCardPreview, MentorCardDetail, MentoringItem, MentoringApprovalStatus }; +export type { MentoringListItem, VerifyStatus }; + +// Response types +export interface MentoringListResponse { + content: MentoringItem[]; + nextPageNumber: number; } -export interface MatchedMentorsResponseContentItemChannelsItem { - type: string; - url: string; +export interface GetMentoringNewCountResponse { + uncheckedCount: number; } -export interface MatchedMentorsResponse { - content: MatchedMentorsResponseContentItem[]; +export interface ApplyMentoringListResponse { + content: MentoringListItem[]; nextPageNumber: number; } -export interface ApplyMentoringResponse { - mentoringId: number; +export interface MentorListResponse { + nextPageNumber: number; + content: MentorCardDetail[]; } -export type ApplyMentoringRequest = Record; - -export interface ConfirmMentoringResponse { - checkedMentoringIds: number[]; +export interface PatchApprovalStatusRequest { + status: MentoringApprovalStatus; + mentoringId: number; } -export type ConfirmMentoringRequest = Record; - -export interface AppliedMentoringsResponseContentItem { +export interface PatchApprovalStatusResponse { mentoringId: number; - profileImageUrl: null; - nickname: string; - isChecked: boolean; - createdAt: string; chatRoomId: number; } -export interface AppliedMentoringsResponse { - content: AppliedMentoringsResponseContentItem[]; - nextPageNumber: number; -} - -export interface MentorListResponseContentItem { - id: number; - profileImageUrl: string; - nickname: string; - country: string; - universityName: string; - term: string; - menteeCount: number; - hasBadge: boolean; - introduction: string; - channels: MentorListResponseContentItemChannelsItem[]; - isApplied: boolean; +export interface PatchCheckMentoringsRequest { + checkedMentoringIds: number[]; } -export interface MentorListResponseContentItemChannelsItem { - type: string; - url: string; +export interface PatchCheckMentoringsResponse { + checkedMentoringIds: number[]; } -export interface MentorListResponse { - nextPageNumber: number; - content: MentorListResponseContentItem[]; +export interface PostApplyMentoringRequest { + mentorId: number; } -export interface MentorDetailResponseChannelsItem { - type: string; - url: string; +export interface PostApplyMentoringResponse { + mentoringId: number; } -export interface MentorDetailResponse { - id: number; - profileImageUrl: string; - nickname: string; +export interface PostMentorApplicationRequest { + interestedCountries: string[]; country: string; universityName: string; - term: string; - menteeCount: number; - hasBadge: boolean; - introduction: string; - channels: MentorDetailResponseChannelsItem[]; - passTip: string; - isApplied: boolean; + studyStatus: "STUDYING" | "PLANNING" | "COMPLETED"; + verificationFile: File; } -export interface MyMentorPageResponseChannelsItem { - type: string; - url: string; -} - -export interface MyMentorPageResponse { - id: number; - profileImageUrl: null; - nickname: string; - country: string; - universityName: string; - term: string; - menteeCount: number; - hasBadge: boolean; - introduction: string; +export interface PutMyMentorProfileRequest { + channels: { type: string; url: string }[]; passTip: string; - channels: MyMentorPageResponseChannelsItem[]; -} - -export type UpdateMyMentorPageResponse = Record; - -export type UpdateMyMentorPageRequest = Record; - -export interface MentoringStatusResponse { - mentoringId: number; -} - -export type MentoringStatusRequest = Record; - -export interface ReceivedMentoringsResponseContentItem { - mentoringId: number; - profileImageUrl: null; - nickname: string; - isChecked: boolean; - verifyStatus: string; - createdAt: string; + introduction: string; } -export interface ReceivedMentoringsResponse { - content: ReceivedMentoringsResponseContentItem[]; - nextPageNumber: number; -} - -export interface UnconfirmedMentoringCountResponse { - uncheckedCount: number; -} +const OFFSET = 5; +const MENTORS_OFFSET = 10; +const MENTEE_OFFSET = 3; export const mentorApi = { - getMatchedMentors: async (params: { defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentee/mentorings/matched-mentors?size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } - ); + // === Mentor (멘토) APIs === + getMentorMyProfile: async (): Promise => { + const res = await axiosInstance.get("/mentor/my"); return res.data; }, - postApplyMentoring: async (params: { data?: ApplyMentoringRequest }): Promise => { - const res = await axiosInstance.post( - `/mentee/mentorings`, params?.data - ); + getMentoringList: async (page: number, size: number = OFFSET): Promise => { + const endpoint = `/mentor/mentorings?size=${size}&page=${page}`; + const res = await axiosInstance.get(endpoint); return res.data; }, - patchConfirmMentoring: async (params: { data?: ConfirmMentoringRequest }): Promise => { - const res = await axiosInstance.patch( - `/mentee/mentorings/check`, params?.data - ); + getMentoringUncheckedCount: async (): Promise => { + const endpoint = "/mentor/mentorings/check"; + const res = await axiosInstance.get(endpoint); return res.data; }, - getAppliedMentorings: async (params: { verifyStatus: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentee/mentorings?verify-status=${params.verifyStatus}&size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } - ); + patchApprovalStatus: async (props: PatchApprovalStatusRequest): Promise => { + const { status, mentoringId } = props; + const res = await axiosInstance.patch(`/mentor/mentorings/${mentoringId}`, { + status, + }); return res.data; }, - getMentorList: async (params: { defaultSize: string | number, defaultPage: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentors?region=미주권&size=${params.defaultSize}&page=${params.defaultPage}`, { params: params?.params } - ); + patchMentorCheckMentorings: async (body: PatchCheckMentoringsRequest): Promise => { + const res = await axiosInstance.patch("/mentor/mentorings/check", body); return res.data; }, - getMentorDetail: async (params: { mentorId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentors/${params.mentorId}`, { params: params?.params } + postMentorApplication: async (body: PostMentorApplicationRequest): Promise => { + const formData = new FormData(); + const applicationData = { + interestedCountries: body.interestedCountries, + country: body.country, + universityName: body.universityName, + studyStatus: body.studyStatus, + }; + formData.append( + "mentorApplicationRequest", + new Blob([JSON.stringify(applicationData)], { type: "application/json" }), ); + formData.append("file", body.verificationFile); + const res = await axiosInstance.post("/mentor/verification", formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); return res.data; }, - getMyMentorPage: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentor/my`, { params: params?.params } - ); + putMyMentorProfile: async (body: PutMyMentorProfileRequest): Promise => { + const res = await axiosInstance.put("/mentor/my", body); return res.data; }, - putUpdateMyMentorPage: async (params: { data?: UpdateMyMentorPageRequest }): Promise => { - const res = await axiosInstance.put( - `/mentor/my`, params?.data + // === Mentee (멘티) APIs === + getApplyMentoringList: async ( + verifyStatus: VerifyStatus, + page: number, + size: number = MENTEE_OFFSET + ): Promise => { + const res = await axiosInstance.get( + `/mentee/mentorings?verify-status=${verifyStatus}&size=${size}&page=${page}`, ); return res.data; }, - patchMentoringStatus: async (params: { mentoringId: string | number, data?: MentoringStatusRequest }): Promise => { - const res = await axiosInstance.patch( - `/mentor/mentorings/${params.mentoringId}`, params?.data - ); + patchMenteeCheckMentorings: async (body: PatchCheckMentoringsRequest): Promise => { + const res = await axiosInstance.patch("/mentee/mentorings/check", body); return res.data; }, - patchConfirmMentoring: async (params: { data?: ConfirmMentoringRequest }): Promise => { - const res = await axiosInstance.patch( - `/mentor/mentorings/check`, params?.data - ); + postApplyMentoring: async (body: PostApplyMentoringRequest): Promise => { + const res = await axiosInstance.post("/mentee/mentorings", body); return res.data; }, - getReceivedMentorings: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentor/mentorings`, { params: params?.params } + // === Mentors (멘토 목록) APIs === + getMentorList: async (region: string, page: number, size: number = MENTORS_OFFSET): Promise => { + const res = await axiosInstance.get( + `/mentors?region=${region}&page=${page}&size=${size}`, ); return res.data; }, - getUnconfirmedMentoringCount: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/mentor/mentorings/check`, { params: params?.params } - ); + getMentorDetail: async (mentorId: number): Promise => { + const res = await axiosInstance.get(`/mentors/${mentorId}`); return res.data; }, - }; \ No newline at end of file diff --git a/src/apis/mentor/getAppliedMentorings.ts b/src/apis/mentor/getAppliedMentorings.ts index 54a9d70b..6c58c95d 100644 --- a/src/apis/mentor/getAppliedMentorings.ts +++ b/src/apis/mentor/getAppliedMentorings.ts @@ -1,14 +1,42 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { mentorApi, AppliedMentoringsResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; - -const useGetAppliedMentorings = (verifyStatus: string | number, defaultSize: string | number, defaultPage: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.appliedMentorings, verifyStatus, defaultSize, defaultPage, params], - queryFn: () => mentorApi.getAppliedMentorings({ verifyStatus, defaultSize, defaultPage, params }), - enabled: !!verifyStatus && !!defaultSize && !!defaultPage, +import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import type { QueryFunctionContext } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, ApplyMentoringListResponse, MentoringListItem, VerifyStatus } from "./api"; + +/** + * @description 신청한 멘토링 목록 조회 훅 (무한 스크롤) + */ +const useGetApplyMentoringList = (verifyStatus: VerifyStatus) => { + return useInfiniteQuery< + ApplyMentoringListResponse, + AxiosError, + MentoringListItem[], + [string, VerifyStatus], + number + >({ + queryKey: [MentorQueryKeys.applyMentoringList, verifyStatus], + queryFn: ({ pageParam = 0 }) => mentorApi.getApplyMentoringList(verifyStatus, pageParam), + initialPageParam: 0, + getNextPageParam: (lastPage) => (lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber), + staleTime: 1000 * 60 * 5, // 5분간 캐시 + select: (data) => data.pages.flatMap((p) => p.content), }); }; -export default useGetAppliedMentorings; \ No newline at end of file +// 멘토링 리스트 프리페치용 훅 +export const usePrefetchApplyMentoringList = () => { + const queryClient = useQueryClient(); + + const prefetchList = (verifyStatus: VerifyStatus) => { + queryClient.prefetchInfiniteQuery({ + queryKey: [MentorQueryKeys.applyMentoringList, verifyStatus], + queryFn: ({ pageParam = 0 }) => mentorApi.getApplyMentoringList(verifyStatus, pageParam as number), + initialPageParam: 0, + staleTime: 1000 * 60 * 5, + }); + }; + + return { prefetchList }; +}; + +export default useGetApplyMentoringList; \ No newline at end of file diff --git a/src/apis/mentor/getMentorDetail.ts b/src/apis/mentor/getMentorDetail.ts index 88f22b9e..42dd8f97 100644 --- a/src/apis/mentor/getMentorDetail.ts +++ b/src/apis/mentor/getMentorDetail.ts @@ -1,13 +1,16 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MentorDetailResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { mentorApi, MentorQueryKeys, MentorCardDetail } from "./api"; -const useGetMentorDetail = (mentorId: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.mentorDetail, mentorId, params], - queryFn: () => mentorApi.getMentorDetail({ mentorId, params }), - enabled: !!mentorId, +/** + * @description 멘토 상세 조회 훅 + */ +const useGetMentorDetail = (mentorId: number | null) => { + return useQuery({ + queryKey: [MentorQueryKeys.mentorDetail, mentorId!], + queryFn: () => mentorApi.getMentorDetail(mentorId!), + enabled: mentorId !== null, + staleTime: 1000 * 60 * 5, // 5분간 캐시 }); }; diff --git a/src/apis/mentor/getMentorList.ts b/src/apis/mentor/getMentorList.ts index 465458c0..08eedc1d 100644 --- a/src/apis/mentor/getMentorList.ts +++ b/src/apis/mentor/getMentorList.ts @@ -1,14 +1,40 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MentorListResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; - -const useGetMentorList = (defaultSize: string | number, defaultPage: string | number, params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.mentorList, defaultSize, defaultPage, params], - queryFn: () => mentorApi.getMentorList({ defaultSize, defaultPage, params }), - enabled: !!defaultSize && !!defaultPage, +import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; +import type { QueryFunctionContext } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, MentorListResponse, MentorCardDetail } from "./api"; + +interface UseGetMentorListRequest { + region?: string; +} + +/** + * @description 멘토 목록 조회 훅 (무한 스크롤) + */ +const useGetMentorList = ({ region = "" }: UseGetMentorListRequest = {}) => { + return useInfiniteQuery({ + queryKey: [MentorQueryKeys.mentorList, region], + queryFn: ({ pageParam = 0 }) => mentorApi.getMentorList(region, pageParam), + initialPageParam: 0, + getNextPageParam: (lastPage) => (lastPage.nextPageNumber === -1 ? undefined : lastPage.nextPageNumber), + staleTime: 1000 * 60 * 5, + select: (data) => data.pages.flatMap((p) => p.content), }); }; +// 탭 프리페치용 훅 +export const usePrefetchMentorList = () => { + const queryClient = useQueryClient(); + + const prefetchMentorList = (region: string) => { + queryClient.prefetchInfiniteQuery({ + queryKey: [MentorQueryKeys.mentorList, region], + queryFn: ({ pageParam = 0 }) => mentorApi.getMentorList(region, pageParam as number), + initialPageParam: 0, + staleTime: 1000 * 60 * 5, + }); + }; + + return { prefetchMentorList }; +}; + export default useGetMentorList; \ No newline at end of file diff --git a/src/apis/mentor/getMyMentorPage.ts b/src/apis/mentor/getMyMentorPage.ts index db6d1b42..26232b21 100644 --- a/src/apis/mentor/getMyMentorPage.ts +++ b/src/apis/mentor/getMyMentorPage.ts @@ -1,13 +1,16 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MyMentorPageResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { mentorApi, MentorQueryKeys, MentorCardPreview } from "./api"; -const useGetMyMentorPage = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.myMentorPage, params], - queryFn: () => mentorApi.getMyMentorPage(params ? { params } : {}), +/** + * @description 멘토 마이 프로필 조회 훅 + */ +const useGetMentorMyProfile = () => { + return useQuery({ + queryKey: [MentorQueryKeys.myMentorProfile], + queryFn: mentorApi.getMentorMyProfile, + staleTime: 1000 * 60 * 5, // 5분간 캐시 }); }; -export default useGetMyMentorPage; \ No newline at end of file +export default useGetMentorMyProfile; \ No newline at end of file diff --git a/src/apis/mentor/getReceivedMentorings.ts b/src/apis/mentor/getReceivedMentorings.ts index 81d875cf..0f401940 100644 --- a/src/apis/mentor/getReceivedMentorings.ts +++ b/src/apis/mentor/getReceivedMentorings.ts @@ -1,13 +1,24 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { mentorApi, ReceivedMentoringsResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, MentoringListResponse, MentoringItem } from "./api"; -const useGetReceivedMentorings = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.receivedMentorings, params], - queryFn: () => mentorApi.getReceivedMentorings(params ? { params } : {}), +const OFFSET = 5; + +/** + * @description 받은 멘토링 목록 조회 훅 (무한 스크롤) + */ +const useGetMentoringList = ({ size = OFFSET }: { size?: number } = {}) => { + return useInfiniteQuery({ + queryKey: [MentorQueryKeys.mentoringList, size], + queryFn: ({ pageParam = 0 }) => mentorApi.getMentoringList(pageParam, size), + initialPageParam: 0, + getNextPageParam: (lastPage) => { + return lastPage.nextPageNumber !== -1 ? lastPage.nextPageNumber : undefined; + }, + refetchInterval: 1000 * 60 * 10, // ⏱️ 10분마다 자동 재요청 + staleTime: 1000 * 60 * 5, // fresh 상태 유지 + select: (data) => data.pages.flatMap((page) => page.content), }); }; -export default useGetReceivedMentorings; \ No newline at end of file +export default useGetMentoringList; \ No newline at end of file diff --git a/src/apis/mentor/getUnconfirmedMentoringCount.ts b/src/apis/mentor/getUnconfirmedMentoringCount.ts index 7c3b9c1b..a5627999 100644 --- a/src/apis/mentor/getUnconfirmedMentoringCount.ts +++ b/src/apis/mentor/getUnconfirmedMentoringCount.ts @@ -1,13 +1,19 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { mentorApi, UnconfirmedMentoringCountResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { mentorApi, MentorQueryKeys } from "./api"; -const useGetUnconfirmedMentoringCount = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.mentor.unconfirmedMentoringCount, params], - queryFn: () => mentorApi.getUnconfirmedMentoringCount(params ? { params } : {}), +/** + * @description 미확인 멘토링 수 조회 훅 + */ +const useGetMentoringUncheckedCount = (isEnable: boolean) => { + return useQuery({ + queryKey: [MentorQueryKeys.mentoringNewCount], + queryFn: mentorApi.getMentoringUncheckedCount, + enabled: isEnable, + refetchInterval: 1000 * 60 * 10, // ⏱️ 10분마다 자동 재요청 + staleTime: 1000 * 60 * 5, // fresh 상태 유지 + select: (data) => data.uncheckedCount, }); }; -export default useGetUnconfirmedMentoringCount; \ No newline at end of file +export default useGetMentoringUncheckedCount; \ No newline at end of file diff --git a/src/apis/mentor/index.ts b/src/apis/mentor/index.ts index cfcceb89..decbddc3 100644 --- a/src/apis/mentor/index.ts +++ b/src/apis/mentor/index.ts @@ -1,12 +1,29 @@ -export { mentorApi } from './api'; -export { default as getAppliedMentorings } from './getAppliedMentorings'; -export { default as getMatchedMentors } from './getMatchedMentors'; -export { default as getMentorDetail } from './getMentorDetail'; -export { default as getMentorList } from './getMentorList'; -export { default as getMyMentorPage } from './getMyMentorPage'; -export { default as getReceivedMentorings } from './getReceivedMentorings'; -export { default as getUnconfirmedMentoringCount } from './getUnconfirmedMentoringCount'; -export { default as patchConfirmMentoring } from './patchConfirmMentoring'; -export { default as patchMentoringStatus } from './patchMentoringStatus'; -export { default as postApplyMentoring } from './postApplyMentoring'; -export { default as putUpdateMyMentorPage } from './putUpdateMyMentorPage'; +export { mentorApi, MentorQueryKeys } from './api'; +export type { + MentorCardPreview, + MentorCardDetail, + MentoringItem, + MentoringApprovalStatus, + MentoringListItem, + VerifyStatus, + PutMyMentorProfileRequest, + PostMentorApplicationRequest +} from './api'; + +// Mentor (멘토) hooks +export { default as useGetMentorMyProfile } from './getMyMentorPage'; +export { default as useGetMentoringList } from './getReceivedMentorings'; +export { default as useGetMentoringUncheckedCount } from './getUnconfirmedMentoringCount'; +export { default as usePatchApprovalStatus } from './patchMentoringStatus'; +export { default as usePatchMentorCheckMentorings } from './patchConfirmMentoring'; +export { default as usePostMentorApplication } from './postMentorApplication'; +export { default as usePutMyMentorProfile } from './putUpdateMyMentorPage'; + +// Mentee (멘티) hooks +export { default as useGetApplyMentoringList, usePrefetchApplyMentoringList } from './getAppliedMentorings'; +export { default as usePatchMenteeCheckMentorings } from './patchMenteeCheckMentorings'; +export { default as usePostApplyMentoring } from './postApplyMentoring'; + +// Mentors (멘토 목록) hooks +export { default as useGetMentorList, usePrefetchMentorList } from './getMentorList'; +export { default as useGetMentorDetail } from './getMentorDetail'; diff --git a/src/apis/mentor/patchConfirmMentoring.ts b/src/apis/mentor/patchConfirmMentoring.ts index 43e07f3f..38cf8042 100644 --- a/src/apis/mentor/patchConfirmMentoring.ts +++ b/src/apis/mentor/patchConfirmMentoring.ts @@ -1,11 +1,22 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { mentorApi, ConfirmMentoringResponse, ConfirmMentoringRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, PatchCheckMentoringsRequest, PatchCheckMentoringsResponse } from "./api"; -const usePatchConfirmMentoring = () => { - return useMutation({ - mutationFn: (data) => mentorApi.patchConfirmMentoring({ data }), +/** + * @description 멘토 멘토링 확인 처리 훅 + */ +const usePatchMentorCheckMentorings = () => { + const queriesClient = useQueryClient(); + return useMutation({ + onSuccess: () => { + // 멘토링 체크 상태 변경 후 멘토링 목록 쿼리 무효화 + Promise.all([ + queriesClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringList] }), + queriesClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringNewCount] }), + ]); + }, + mutationFn: mentorApi.patchMentorCheckMentorings, }); }; -export default usePatchConfirmMentoring; \ No newline at end of file +export default usePatchMentorCheckMentorings; \ No newline at end of file diff --git a/src/apis/mentor/patchMenteeCheckMentorings.ts b/src/apis/mentor/patchMenteeCheckMentorings.ts new file mode 100644 index 00000000..ecb9a26c --- /dev/null +++ b/src/apis/mentor/patchMenteeCheckMentorings.ts @@ -0,0 +1,14 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, PatchCheckMentoringsRequest, PatchCheckMentoringsResponse } from "./api"; + +/** + * @description 멘티 멘토링 확인 처리 훅 + */ +const usePatchMenteeCheckMentorings = () => { + return useMutation({ + mutationFn: mentorApi.patchMenteeCheckMentorings, + }); +}; + +export default usePatchMenteeCheckMentorings; diff --git a/src/apis/mentor/patchMentoringStatus.ts b/src/apis/mentor/patchMentoringStatus.ts index eec04d74..723f9006 100644 --- a/src/apis/mentor/patchMentoringStatus.ts +++ b/src/apis/mentor/patchMentoringStatus.ts @@ -1,11 +1,55 @@ +import { useRouter } from "next/navigation"; import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { mentorApi, MentoringStatusResponse, MentoringStatusRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, PatchApprovalStatusRequest, PatchApprovalStatusResponse, MentoringApprovalStatus } from "./api"; +import { customAlert } from "@/lib/zustand/useAlertModalStore"; +import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; +import { IconSmile, IconUnSmile } from "@/public/svgs/mentor"; -const usePatchMentoringStatus = () => { - return useMutation({ - mutationFn: (variables) => mentorApi.patchMentoringStatus(variables), +/** + * @description 멘토링 승인/거절 훅 + */ +const usePatchApprovalStatus = () => { + const router = useRouter(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: mentorApi.patchApprovalStatus, + onSuccess: async (data, variables) => { + // 멘토링 상태 변경 후 쿼리 무효화 + await Promise.all([ + queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringList] }), + queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringNewCount] }), + ]); + + if (variables.status === MentoringApprovalStatus.REJECTED) { + customAlert({ + title: "멘티 신청을 거절했어요.", + icon: IconUnSmile, + content: "현재까지 누적해서 거절했어요. 누적 5회 거절 시 활동에 제약이 있으니 유의해주세요.", + buttonText: "닫기", + }); + } else if (variables.status === MentoringApprovalStatus.APPROVED) { + const ok = await customConfirm({ + title: "멘티 신청이 완료되었어요!", + content: "지금 바로 멘티에게 메시지를 전송해보세요", + icon: IconSmile, + rejectMessage: "닫기", + approveMessage: "1:1 채팅 바로가기", + }); + if (ok) { + router.push(`/mentor/chat/${data.chatRoomId}`); + } + } + }, + onError: (error) => { + customAlert({ + title: "멘토링 상태 변경 실패", + content: "멘토링 상태 변경 중 오류가 발생했습니다. 다시 시도해주세요.", + buttonText: "확인", + }); + }, }); }; -export default usePatchMentoringStatus; \ No newline at end of file +export default usePatchApprovalStatus; \ No newline at end of file diff --git a/src/apis/mentor/postApplyMentoring.ts b/src/apis/mentor/postApplyMentoring.ts index f5a54e52..bee41d33 100644 --- a/src/apis/mentor/postApplyMentoring.ts +++ b/src/apis/mentor/postApplyMentoring.ts @@ -1,10 +1,22 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { mentorApi, ApplyMentoringResponse, ApplyMentoringRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, PostApplyMentoringRequest, PostApplyMentoringResponse } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; +/** + * @description 멘토링 신청 훅 + */ const usePostApplyMentoring = () => { - return useMutation({ - mutationFn: (data) => mentorApi.postApplyMentoring({ data }), + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: mentorApi.postApplyMentoring, + onSuccess: async () => { + // 멘토링 신청 후 멘토 목록을 새로고침 + await queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.applyMentoringList] }); + }, + onError: () => { + toast.error("멘토 신청에 실패했습니다. 다시 시도해주세요."); + }, }); }; diff --git a/src/apis/mentor/postMentorApplication.ts b/src/apis/mentor/postMentorApplication.ts new file mode 100644 index 00000000..eee51af0 --- /dev/null +++ b/src/apis/mentor/postMentorApplication.ts @@ -0,0 +1,18 @@ +import { AxiosError } from "axios"; +import { useMutation } from "@tanstack/react-query"; +import { mentorApi, PostMentorApplicationRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; + +/** + * @description 멘토 신청 훅 + */ +const usePostMentorApplication = () => { + return useMutation({ + mutationFn: mentorApi.postMentorApplication, + onError: (error) => { + toast.error("멘토 신청에 실패했습니다. 다시 시도해주세요."); + }, + }); +}; + +export default usePostMentorApplication; diff --git a/src/apis/mentor/putUpdateMyMentorPage.ts b/src/apis/mentor/putUpdateMyMentorPage.ts index a3cd5a31..24fdbb07 100644 --- a/src/apis/mentor/putUpdateMyMentorPage.ts +++ b/src/apis/mentor/putUpdateMyMentorPage.ts @@ -1,11 +1,22 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { mentorApi, UpdateMyMentorPageResponse, UpdateMyMentorPageRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { mentorApi, MentorQueryKeys, PutMyMentorProfileRequest } from "./api"; -const usePutUpdateMyMentorPage = () => { - return useMutation({ - mutationFn: (data) => mentorApi.putUpdateMyMentorPage({ data }), +/** + * @description 내 멘토 프로필 수정 훅 + */ +const usePutMyMentorProfile = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: mentorApi.putMyMentorProfile, + onSuccess: () => { + // 멘토 프로필 데이터를 stale로 만들어 다음 요청 시 새로운 데이터를 가져오도록 함 + queryClient.invalidateQueries({ + queryKey: [MentorQueryKeys.myMentorProfile], + }); + }, }); }; -export default usePutUpdateMyMentorPage; \ No newline at end of file +export default usePutMyMentorProfile; \ No newline at end of file diff --git a/src/apis/news/api.ts b/src/apis/news/api.ts index 13a68b0b..645a4e03 100644 --- a/src/apis/news/api.ts +++ b/src/apis/news/api.ts @@ -1,79 +1,106 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; - -export interface NewsListResponseNewsResponseListItem { - id: number; - title: string; - description: string; - url: string; - thumbnailUrl: string; - updatedAt: string; -} - -export interface NewsListResponse { - newsResponseList: NewsListResponseNewsResponseListItem[]; +import { Article } from "@/types/news"; +import { ArticleFormData } from "@/components/mentor/ArticleBottomSheetModal/lib/schema"; + +// ====== Query Keys ====== +export const NewsQueryKeys = { + articleList: "articleList", + postAddArticle: "postAddArticle", + putModifyArticle: "putModifyArticle", +} as const; + +// ====== Types ====== +export interface ArticleListResponse { + newsResponseList: Article[]; } -export interface NewsResponse { - id: number; +export interface PostArticleLikeResponse { + isLiked: boolean; + likeCount: number; } -export interface UpdateNewsResponse { - id: number; +export interface DeleteArticleLikeResponse { + isLiked: boolean; + likeCount: number; } -export type UpdateNewsRequest = Record; - -export type LikeNewsResponse = void; +export type UsePostAddArticleRequest = ArticleFormData; -export type LikeNewsRequest = Record; - -export interface CreateNewsResponse { - id: number; -} - -export type CreateNewsRequest = Record; +export type UsePutModifyArticleRequest = { + body: ArticleFormData & { isImageDeleted?: boolean }; + articleId: number; +}; +// ====== API Functions ====== export const newsApi = { - getNewsList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/news?author-id=6`, { params: params?.params } - ); - return res.data; + /** + * 아티클 목록 조회 + */ + getArticleList: async (userId: number): Promise => { + const response: AxiosResponse = await axiosInstance.get(`/news?author-id=${userId}`); + return response.data; }, - deleteNews: async (params: { newsId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/news/${params.newsId}` - ); - return res.data; + /** + * 아티클 추가 + */ + postAddArticle: async (body: UsePostAddArticleRequest): Promise
=> { + const newsCreateRequest = { + title: body.title, + description: body.description, + url: body.url || "", + }; + + const formData = new FormData(); + formData.append("newsCreateRequest", new Blob([JSON.stringify(newsCreateRequest)], { type: "application/json" })); + if (body.file) { + formData.append("file", body.file); + } + const response: AxiosResponse
= await axiosInstance.post("/news", formData); + return response.data; }, - putUpdateNews: async (params: { newsId: string | number, data?: UpdateNewsRequest }): Promise => { - const res = await axiosInstance.put( - `/news/${params.newsId}`, params?.data - ); - return res.data; + /** + * 아티클 수정 + */ + putModifyArticle: async (props: UsePutModifyArticleRequest): Promise
=> { + const { body, articleId } = props; + const newsUpdateRequest = { + title: body.title, + description: body.description, + url: body.url || "", + resetToDefaultImage: body.isImageDeleted === true, + }; + const formData = new FormData(); + formData.append("newsUpdateRequest", new Blob([JSON.stringify(newsUpdateRequest)], { type: "application/json" })); + if (body.file) formData.append("file", body.file); + + const response: AxiosResponse
= await axiosInstance.put(`/news/${articleId}`, formData); + return response.data; }, - postLikeNews: async (params: { newsId: string | number, data?: LikeNewsRequest }): Promise => { - const res = await axiosInstance.post( - `/news/${params.newsId}/like`, params?.data - ); - return res.data; + /** + * 아티클 삭제 + */ + deleteArticle: async (articleId: number): Promise => { + const response: AxiosResponse = await axiosInstance.delete(`/news/${articleId}`); + return response.data; }, - deleteLikeNews: async (params: { newsId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/news/${params.newsId}/like` - ); - return res.data; + /** + * 아티클 좋아요 + */ + postArticleLike: async (articleId: number): Promise => { + const response: AxiosResponse = await axiosInstance.post(`/news/${articleId}/like`); + return response.data; }, - postCreateNews: async (params: { data?: CreateNewsRequest }): Promise => { - const res = await axiosInstance.post( - `/news`, params?.data - ); - return res.data; + /** + * 아티클 좋아요 취소 + */ + deleteArticleLike: async (articleId: number): Promise => { + const response: AxiosResponse = await axiosInstance.delete(`/news/${articleId}/like`); + return response.data; }, - }; \ No newline at end of file diff --git a/src/apis/news/deleteLikeNews.ts b/src/apis/news/deleteLikeNews.ts index 9e0ad49c..26041356 100644 --- a/src/apis/news/deleteLikeNews.ts +++ b/src/apis/news/deleteLikeNews.ts @@ -1,11 +1,51 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { newsApi, LikeNewsResponse, LikeNewsRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { newsApi, NewsQueryKeys, DeleteArticleLikeResponse, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; -const useDeleteLikeNews = () => { - return useMutation({ - mutationFn: (variables) => newsApi.deleteLikeNews(variables), +type ArticleLikeMutationContext = { + previousArticleList?: ArticleListResponse; +}; + +/** + * @description 아티클 좋아요 취소 훅 + */ +const useDeleteArticleLike = (userId: number | null) => { + const queryClient = useQueryClient(); + const queryKey = [NewsQueryKeys.articleList, userId]; + + return useMutation, number, ArticleLikeMutationContext>({ + mutationFn: newsApi.deleteArticleLike, + + onMutate: async (unlikedArticleId) => { + await queryClient.cancelQueries({ queryKey }); + + const previousArticleList = queryClient.getQueryData(queryKey); + + queryClient.setQueryData(queryKey, (oldData) => { + if (!oldData) return { newsResponseList: [] }; + return { + newsResponseList: oldData.newsResponseList.map((article) => + article.id === unlikedArticleId + ? { + ...article, + isLiked: false, + likeCount: Math.max(0, (article.likeCount ?? 1) - 1), + } + : article, + ), + }; + }); + + return { previousArticleList }; + }, + + onError: (err, variables, context) => { + if (context?.previousArticleList) { + queryClient.setQueryData(queryKey, context.previousArticleList); + } + }, }); }; -export default useDeleteLikeNews; \ No newline at end of file +export default useDeleteArticleLike; \ No newline at end of file diff --git a/src/apis/news/deleteNews.ts b/src/apis/news/deleteNews.ts index 854b6392..fdc6fc74 100644 --- a/src/apis/news/deleteNews.ts +++ b/src/apis/news/deleteNews.ts @@ -1,11 +1,50 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { newsApi, NewsResponse, NewsRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { newsApi, NewsQueryKeys, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; +import { toast } from "@/lib/zustand/useToastStore"; -const useDeleteNews = () => { - return useMutation({ - mutationFn: (variables) => newsApi.deleteNews(variables), +type ArticleDeleteMutationContext = { + previousArticleList?: Article[]; +}; + +/** + * @description 아티클 삭제 훅 + */ +const useDeleteArticle = (userId: number | null) => { + const queryClient = useQueryClient(); + const queryKey = [NewsQueryKeys.articleList, userId]; + + return useMutation, number, ArticleDeleteMutationContext>({ + mutationFn: newsApi.deleteArticle, + + onMutate: async (deletedArticleId) => { + await queryClient.cancelQueries({ queryKey }); + + const previousArticleList = queryClient.getQueryData(queryKey); + + queryClient.setQueryData(queryKey, (oldData) => { + if (!oldData) return { newsResponseList: [] }; + return { + newsResponseList: oldData.newsResponseList.filter((article) => article.id !== deletedArticleId), + }; + }); + + return { previousArticleList }; + }, + + onError: (error, variables, context) => { + if (context?.previousArticleList) { + queryClient.setQueryData(queryKey, context.previousArticleList); + } + toast.error("아티클 삭제에 실패했습니다. 다시 시도해주세요."); + console.error("Failed to delete article:", error); + }, + + onSettled: () => { + queryClient.invalidateQueries({ queryKey }); + }, }); }; -export default useDeleteNews; \ No newline at end of file +export default useDeleteArticle; \ No newline at end of file diff --git a/src/apis/news/getNewsList.ts b/src/apis/news/getNewsList.ts index bd2f587d..8bcc4f2a 100644 --- a/src/apis/news/getNewsList.ts +++ b/src/apis/news/getNewsList.ts @@ -1,13 +1,24 @@ import { AxiosError } from "axios"; import { useQuery } from "@tanstack/react-query"; -import { newsApi, NewsListResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; +import { newsApi, NewsQueryKeys, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; -const useGetNewsList = (params?: Record) => { - return useQuery({ - queryKey: [QueryKeys.news.newsList, params], - queryFn: () => newsApi.getNewsList(params ? { params } : {}), +/** + * @description 아티클 목록 조회 훅 + */ +const useGetArticleList = (userId: number) => { + return useQuery({ + queryKey: [NewsQueryKeys.articleList, userId], + queryFn: () => { + if (userId === null) { + return Promise.reject(new Error("User ID is null")); + } + return newsApi.getArticleList(userId); + }, + staleTime: 1000 * 60 * 10, // 10분 + enabled: userId !== null && userId !== 0, + select: (data) => data.newsResponseList, }); }; -export default useGetNewsList; \ No newline at end of file +export default useGetArticleList; \ No newline at end of file diff --git a/src/apis/news/index.ts b/src/apis/news/index.ts index dc313953..0e1da051 100644 --- a/src/apis/news/index.ts +++ b/src/apis/news/index.ts @@ -1,7 +1,16 @@ -export { newsApi } from './api'; -export { default as deleteLikeNews } from './deleteLikeNews'; -export { default as deleteNews } from './deleteNews'; -export { default as getNewsList } from './getNewsList'; -export { default as postCreateNews } from './postCreateNews'; -export { default as postLikeNews } from './postLikeNews'; -export { default as putUpdateNews } from './putUpdateNews'; +export { newsApi, NewsQueryKeys } from './api'; +export type { + ArticleListResponse, + PostArticleLikeResponse, + DeleteArticleLikeResponse, + UsePostAddArticleRequest, + UsePutModifyArticleRequest +} from './api'; + +// News (아티클) hooks +export { default as useGetArticleList } from './getNewsList'; +export { default as usePostAddArticle } from './postCreateNews'; +export { default as usePutModifyArticle } from './putUpdateNews'; +export { default as useDeleteArticle } from './deleteNews'; +export { default as usePostArticleLike } from './postLikeNews'; +export { default as useDeleteArticleLike } from './deleteLikeNews'; diff --git a/src/apis/news/postCreateNews.ts b/src/apis/news/postCreateNews.ts index 075351f2..fd5dfe8a 100644 --- a/src/apis/news/postCreateNews.ts +++ b/src/apis/news/postCreateNews.ts @@ -1,11 +1,57 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { newsApi, CreateNewsResponse, CreateNewsRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { newsApi, NewsQueryKeys, UsePostAddArticleRequest, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; +import { toast } from "@/lib/zustand/useToastStore"; +import ArticleThumbUrlPng from "@/public/images/article-thumb.png"; -const usePostCreateNews = () => { - return useMutation({ - mutationFn: (data) => newsApi.postCreateNews({ data }), +type ArticleMutationContext = { + previousArticleContainer?: ArticleListResponse; +}; + +/** + * @description 아티클 추가 훅 + */ +const usePostAddArticle = (userId: number | null) => { + const queryClient = useQueryClient(); + const queryKey = [NewsQueryKeys.articleList, userId]; + + return useMutation, UsePostAddArticleRequest, ArticleMutationContext>({ + mutationFn: newsApi.postAddArticle, + onMutate: async (newArticle) => { + await queryClient.cancelQueries({ queryKey }); + + const previousArticleContainer = queryClient.getQueryData(queryKey); + + queryClient.setQueryData(queryKey, (oldData) => { + if (!oldData) return { newsResponseList: [] }; + + const optimisticArticle: Article = { + id: Date.now(), // 임시 ID + title: newArticle.title, + description: newArticle.description, + url: newArticle.url || "", + thumbnailUrl: newArticle.file ? URL.createObjectURL(newArticle.file) : ArticleThumbUrlPng.src, + updatedAt: new Date().toISOString(), + }; + + return { + newsResponseList: [optimisticArticle, ...oldData.newsResponseList], + }; + }); + return { previousArticleContainer }; + }, + onError: (error, variables, context) => { + const errorMessage = error.response?.data?.message || ""; + if (context?.previousArticleContainer) { + queryClient.setQueryData(queryKey, context.previousArticleContainer); + } + toast.error("아티클 추가에 실패했습니다: " + errorMessage); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey }); + }, }); }; -export default usePostCreateNews; \ No newline at end of file +export default usePostAddArticle; \ No newline at end of file diff --git a/src/apis/news/postLikeNews.ts b/src/apis/news/postLikeNews.ts index 9aaf4c53..606499f1 100644 --- a/src/apis/news/postLikeNews.ts +++ b/src/apis/news/postLikeNews.ts @@ -1,11 +1,51 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { newsApi, LikeNewsResponse, LikeNewsRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { newsApi, NewsQueryKeys, PostArticleLikeResponse, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; -const usePostLikeNews = () => { - return useMutation({ - mutationFn: (variables) => newsApi.postLikeNews(variables), +type ArticleLikeMutationContext = { + previousArticleList?: Article[]; +}; + +/** + * @description 아티클 좋아요 훅 + */ +const usePostArticleLike = (userId: number | null) => { + const queryClient = useQueryClient(); + const queryKey = [NewsQueryKeys.articleList, userId]; + + return useMutation, number, ArticleLikeMutationContext>({ + mutationFn: newsApi.postArticleLike, + + onMutate: async (likedArticleId) => { + await queryClient.cancelQueries({ queryKey }); + + const previousArticleList = queryClient.getQueryData(queryKey); + + queryClient.setQueryData(queryKey, (oldData) => { + if (!oldData) return { newsResponseList: [] }; + return { + newsResponseList: oldData.newsResponseList.map((article) => + article.id === likedArticleId + ? { + ...article, + isLiked: true, + likeCount: (article.likeCount ?? 0) + 1, + } + : article, + ), + }; + }); + + return { previousArticleList }; + }, + + onError: (err, variables, context) => { + if (context?.previousArticleList) { + queryClient.setQueryData(queryKey, context.previousArticleList); + } + }, }); }; -export default usePostLikeNews; \ No newline at end of file +export default usePostArticleLike; \ No newline at end of file diff --git a/src/apis/news/putUpdateNews.ts b/src/apis/news/putUpdateNews.ts index d7fedea7..6c31f9b4 100644 --- a/src/apis/news/putUpdateNews.ts +++ b/src/apis/news/putUpdateNews.ts @@ -1,11 +1,59 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { newsApi, UpdateNewsResponse, UpdateNewsRequest } from "./api"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { newsApi, NewsQueryKeys, UsePutModifyArticleRequest, ArticleListResponse } from "./api"; +import { Article } from "@/types/news"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePutUpdateNews = () => { - return useMutation({ - mutationFn: (variables) => newsApi.putUpdateNews(variables), +type ArticleMutationContext = { + previousArticleList?: Article[]; +}; + +/** + * @description 아티클 수정 훅 + */ +const usePutModifyArticle = (userId: number | null) => { + const queryClient = useQueryClient(); + const queryKey = [NewsQueryKeys.articleList, userId]; + + return useMutation, UsePutModifyArticleRequest, ArticleMutationContext>({ + mutationFn: newsApi.putModifyArticle, + onMutate: async (variables) => { + await queryClient.cancelQueries({ queryKey }); + const previousArticleList = queryClient.getQueryData(queryKey); + + queryClient.setQueryData(queryKey, (oldData) => { + if (!oldData) return { newsResponseList: [] }; + + return { + newsResponseList: oldData.newsResponseList.map((article) => { + if (article.id === variables.articleId) { + const optimisticData = variables.body; + + return { + ...article, + title: optimisticData.title, + description: optimisticData.description, + url: optimisticData.url || "", + thumbnailUrl: optimisticData.file ? URL.createObjectURL(optimisticData.file) : article.thumbnailUrl, + }; + } + return article; + }), + }; + }); + return { previousArticleList }; + }, + onError: (error, variables, context) => { + const errorMessage = error.response?.data?.message || ""; + if (context?.previousArticleList) { + queryClient.setQueryData(queryKey, context.previousArticleList); + } + toast.error("아티클 수정에 실패했습니다." + errorMessage); + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey }); + }, }); }; -export default usePutUpdateNews; \ No newline at end of file +export default usePutModifyArticle; \ No newline at end of file diff --git a/src/apis/reports/api.ts b/src/apis/reports/api.ts index 39bd649c..b31567c8 100644 --- a/src/apis/reports/api.ts +++ b/src/apis/reports/api.ts @@ -1,15 +1,21 @@ +import { AxiosResponse } from "axios"; import { axiosInstance } from "@/utils/axiosInstance"; +import { ReportType } from "@/types/reports"; -export type ReportResponse = Record; - -export type ReportRequest = Record; +// ====== Types ====== +export interface UsePostReportsRequest { + targetType: "POST"; // 지금은 게시글 신고 기능만 존재 + targetId: number; // 신고하려는 리소스의 ID + reportType: ReportType; +} +// ====== API Functions ====== export const reportsApi = { - postReport: async (params: { data?: ReportRequest }): Promise => { - const res = await axiosInstance.post( - `/reports`, params?.data - ); - return res.data; + /** + * 신고 등록 + */ + postReport: async (body: UsePostReportsRequest): Promise => { + const response: AxiosResponse = await axiosInstance.post(`/reports`, body); + return response.data; }, - }; \ No newline at end of file diff --git a/src/apis/reports/index.ts b/src/apis/reports/index.ts index c8d54d63..684e414a 100644 --- a/src/apis/reports/index.ts +++ b/src/apis/reports/index.ts @@ -1,2 +1,3 @@ export { reportsApi } from './api'; -export { default as postReport } from './postReport'; +export type { UsePostReportsRequest } from './api'; +export { default as usePostReports } from './postReport'; diff --git a/src/apis/reports/postReport.ts b/src/apis/reports/postReport.ts index 76fd804f..10222923 100644 --- a/src/apis/reports/postReport.ts +++ b/src/apis/reports/postReport.ts @@ -1,11 +1,24 @@ +import { useRouter } from "next/navigation"; import { AxiosError } from "axios"; import { useMutation } from "@tanstack/react-query"; -import { reportsApi, ReportResponse, ReportRequest } from "./api"; +import { reportsApi, UsePostReportsRequest } from "./api"; +import { toast } from "@/lib/zustand/useToastStore"; -const usePostReport = () => { - return useMutation({ - mutationFn: (data) => reportsApi.postReport({ data }), +/** + * @description 신고 등록 훅 + */ +const usePostReports = () => { + const router = useRouter(); + return useMutation, UsePostReportsRequest>({ + mutationFn: reportsApi.postReport, + onSuccess: () => { + toast.success("신고가 성공적으로 등록되었습니다."); + router.back(); + }, + onError: (error) => { + toast.error("신고 등록에 실패했습니다. 잠시 후 다시 시도해주세요."); + }, }); }; -export default usePostReport; \ No newline at end of file +export default usePostReports; \ No newline at end of file diff --git a/src/app/community/[boardCode]/CommunityPageContent.tsx b/src/app/community/[boardCode]/CommunityPageContent.tsx index cd11803f..286a8f13 100644 --- a/src/app/community/[boardCode]/CommunityPageContent.tsx +++ b/src/app/community/[boardCode]/CommunityPageContent.tsx @@ -11,7 +11,7 @@ import PostWriteButton from "./PostWriteButton"; import { COMMUNITY_BOARDS, COMMUNITY_CATEGORIES } from "@/constants/community"; -import useGetPostList from "@/api/boards/clients/useGetPostList"; +import { useGetPostList } from "@/apis/community"; interface CommunityPageContentProps { boardCode: string; diff --git a/src/app/community/[boardCode]/[postId]/CommentInput.tsx b/src/app/community/[boardCode]/[postId]/CommentInput.tsx index 67c18a60..c3b219ad 100644 --- a/src/app/community/[boardCode]/[postId]/CommentInput.tsx +++ b/src/app/community/[boardCode]/[postId]/CommentInput.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; -import useCreateComment from "@/api/community/client/useCreateComment"; +import { useCreateComment } from "@/apis/community"; import { IconCloseFilled, IconFlight } from "@/public/svgs"; type CommentInputProps = { diff --git a/src/app/community/[boardCode]/[postId]/CommentSection.tsx b/src/app/community/[boardCode]/[postId]/CommentSection.tsx index 8aa1fc27..66ee10cd 100644 --- a/src/app/community/[boardCode]/[postId]/CommentSection.tsx +++ b/src/app/community/[boardCode]/[postId]/CommentSection.tsx @@ -14,7 +14,7 @@ import CommentInput from "./CommentInput"; import { Comment as CommentType, CommunityUser } from "@/types/community"; -import useDeleteComment from "@/api/community/client/useDeleteComment"; +import { useDeleteComment } from "@/apis/community"; import { IconMoreVertFilled, IconSubComment } from "@/public/svgs"; type CommentSectionProps = { diff --git a/src/app/community/[boardCode]/[postId]/Content.tsx b/src/app/community/[boardCode]/[postId]/Content.tsx index 9b6b4c90..895977f1 100644 --- a/src/app/community/[boardCode]/[postId]/Content.tsx +++ b/src/app/community/[boardCode]/[postId]/Content.tsx @@ -9,8 +9,7 @@ import LinkifyText from "@/components/ui/LinkifyText"; import { PostImage as PostImageType, Post as PostType } from "@/types/community"; -import useDeleteLike from "@/api/community/client/useDeleteLike"; -import usePostLike from "@/api/community/client/usePostLike"; +import { useDeleteLike, usePostLike } from "@/apis/community"; import { IconCloseFilled, IconPostLikeFilled, IconPostLikeOutline } from "@/public/svgs"; import { IconCommunication } from "@/public/svgs/community"; diff --git a/src/app/community/[boardCode]/[postId]/KebabMenu.tsx b/src/app/community/[boardCode]/[postId]/KebabMenu.tsx index 884e19e1..a38d8479 100644 --- a/src/app/community/[boardCode]/[postId]/KebabMenu.tsx +++ b/src/app/community/[boardCode]/[postId]/KebabMenu.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react"; import ReportPanel from "@/components/ui/ReportPanel"; -import useDeletePost from "@/api/community/client/useDeletePost"; +import { useDeletePost } from "@/apis/community"; import { toast } from "@/lib/zustand/useToastStore"; import { IconSetting } from "@/public/svgs/mentor"; diff --git a/src/app/community/[boardCode]/[postId]/PostPageContent.tsx b/src/app/community/[boardCode]/[postId]/PostPageContent.tsx index a80ee957..d31e3b66 100644 --- a/src/app/community/[boardCode]/[postId]/PostPageContent.tsx +++ b/src/app/community/[boardCode]/[postId]/PostPageContent.tsx @@ -9,7 +9,7 @@ import CommentSection from "./CommentSection"; import Content from "./Content"; import KebabMenu from "./KebabMenu"; -import useGetPostDetail from "@/api/community/client/useGetPostDetail"; +import { useGetPostDetail } from "@/apis/community"; interface PostPageContentProps { boardCode: string; diff --git a/src/app/community/[boardCode]/[postId]/modify/PostModifyContent.tsx b/src/app/community/[boardCode]/[postId]/modify/PostModifyContent.tsx index bf9a018f..39271dca 100644 --- a/src/app/community/[boardCode]/[postId]/modify/PostModifyContent.tsx +++ b/src/app/community/[boardCode]/[postId]/modify/PostModifyContent.tsx @@ -7,7 +7,7 @@ import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; import PostModifyForm from "./PostModifyForm"; -import useGetPostDetail from "@/api/community/client/useGetPostDetail"; +import { useGetPostDetail } from "@/apis/community"; interface PostModifyContentProps { boardCode: string; diff --git a/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx b/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx index 936c60de..6592a171 100644 --- a/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx +++ b/src/app/community/[boardCode]/[postId]/modify/PostModifyForm.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; -import useUpdatePost from "@/api/community/client/useUpdatePost"; +import { useUpdatePost } from "@/apis/community"; import { IconArrowBackFilled, IconImage, IconPostCheckboxFilled, IconPostCheckboxOutlined } from "@/public/svgs"; type PostModifyFormProps = { diff --git a/src/app/community/[boardCode]/create/PostForm.tsx b/src/app/community/[boardCode]/create/PostForm.tsx index cf8d4245..19233cda 100644 --- a/src/app/community/[boardCode]/create/PostForm.tsx +++ b/src/app/community/[boardCode]/create/PostForm.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; -import useCreatePost from "@/api/community/client/useCreatePost"; +import { useCreatePost } from "@/apis/community"; import { IconImage, IconPostCheckboxFilled, IconPostCheckboxOutlined } from "@/public/svgs"; type PostFormProps = { diff --git a/src/app/community/[boardCode]/page.tsx b/src/app/community/[boardCode]/page.tsx index 3a4cde38..ea02d013 100644 --- a/src/app/community/[boardCode]/page.tsx +++ b/src/app/community/[boardCode]/page.tsx @@ -6,8 +6,7 @@ import CommunityPageContent from "./CommunityPageContent"; import { COMMUNITY_BOARDS } from "@/constants/community"; -import { QueryKeys } from "@/api/boards/clients/QueryKeys"; -import { getPostList } from "@/api/boards/server/getPostList"; +import { CommunityQueryKeys, getPostListServer } from "@/apis/community"; import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query"; export const metadata: Metadata = { @@ -40,11 +39,11 @@ const CommunityPage = async ({ params }: CommunityPageProps) => { const defaultCategory = "전체"; // 서버에서 데이터 prefetch (ISR - 수동 revalidate만) - const result = await getPostList({ boardCode, category: defaultCategory, revalidate: false }); + const result = await getPostListServer({ boardCode, category: defaultCategory, revalidate: false }); if (result.ok) { // React Query 캐시에 데이터 설정 (서버 fetch와 동일한 category 사용) - queryClient.setQueryData([QueryKeys.postList, boardCode, defaultCategory], { + queryClient.setQueryData([CommunityQueryKeys.postList, boardCode, defaultCategory], { data: result.data, }); } diff --git a/src/app/login/LoginContent.tsx b/src/app/login/LoginContent.tsx index 4c83d0b9..c78022b8 100644 --- a/src/app/login/LoginContent.tsx +++ b/src/app/login/LoginContent.tsx @@ -84,7 +84,11 @@ const LoginContent = () => { >>>>>> cbc333da (refactor: complete API migration from src/api to src/apis) {...register("email", { onChange: handleEmailChange, })} @@ -101,7 +105,11 @@ const LoginContent = () => { >>>>>> cbc333da (refactor: complete API migration from src/api to src/apis) {...register("password")} onKeyDown={handleKeyDown} /> diff --git a/src/app/mentor/[id]/_ui/MentorDetialContent/_ui/MentorArticle/_hooks/useLikeToggle.ts b/src/app/mentor/[id]/_ui/MentorDetialContent/_ui/MentorArticle/_hooks/useLikeToggle.ts index 0f1df4f4..12d67b6f 100644 --- a/src/app/mentor/[id]/_ui/MentorDetialContent/_ui/MentorArticle/_hooks/useLikeToggle.ts +++ b/src/app/mentor/[id]/_ui/MentorDetialContent/_ui/MentorArticle/_hooks/useLikeToggle.ts @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; -import useDeleteArticleLike from "@/api/news/client/useDeleteArticleLike"; -import usePostArticleLike from "@/api/news/client/usePostArticleLike"; +import { useDeleteArticleLike, usePostArticleLike } from "@/apis/news"; const useLikeToggle = (articleId: number, mentorId: number, articleIsLiked?: boolean) => { const { mutate: postArticleLike } = usePostArticleLike(mentorId); diff --git a/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx b/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx index 26c75528..c0d5ca7a 100644 --- a/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx +++ b/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx @@ -12,9 +12,8 @@ import MentorArticle from "./_ui/MentorArticle"; import { ChannelType } from "@/types/mentor"; -import usePostApplyMentoring from "@/api/mentee/client/usePostApplyMentoring"; -import useGetMentorDetail from "@/api/mentors/client/useGetMentorDetail"; -import useGetArticleList from "@/api/news/client/useGetArticleList"; +import { usePostApplyMentoring, useGetMentorDetail } from "@/apis/mentor"; +import { useGetArticleList } from "@/apis/news"; interface MentorDetailContentProps { mentorId: number; diff --git a/src/app/mentor/_ui/MentorClient/_ui/MenteePageTabs/index.tsx b/src/app/mentor/_ui/MentorClient/_ui/MenteePageTabs/index.tsx index a023e722..d25aa21c 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MenteePageTabs/index.tsx +++ b/src/app/mentor/_ui/MentorClient/_ui/MenteePageTabs/index.tsx @@ -10,8 +10,8 @@ import TabSelector from "@/components/ui/TabSelector"; import { VerifyStatus } from "@/types/mentee"; import { MenteeTab } from "@/types/mentor"; -import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; -import useGetMenteeMentoringList from "@/api/mentee/client/useGetApplyMentoringList"; +import { useGetChatRooms } from "@/apis/chat"; +import { useGetApplyMentoringList as useGetMenteeMentoringList } from "@/apis/mentor"; import { IconDirectionRight } from "@/public/svgs/mentor"; const MenteePageTabs = () => { diff --git a/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/_hooks/usePrefetchMentorFindTab.ts b/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/_hooks/usePrefetchMentorFindTab.ts index 03da1d71..4af8bafc 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/_hooks/usePrefetchMentorFindTab.ts +++ b/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/_hooks/usePrefetchMentorFindTab.ts @@ -2,7 +2,7 @@ import { useEffect } from "react"; import { FilterTab } from "@/types/mentor"; -import { usePrefetchMentorList } from "@/api/mentors/client/useGetMentorList"; +import { usePrefetchMentorList } from "@/apis/mentor"; const usePrefetchMentorFindTab = () => { const { prefetchMentorList } = usePrefetchMentorList(); diff --git a/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx b/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx index 787b03f1..feffda6b 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx +++ b/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx @@ -11,7 +11,7 @@ import useSelectedTab from "./_hooks/useSelectedTab"; import { FilterTab } from "@/types/mentor"; -import useGetMentorList from "@/api/mentors/client/useGetMentorList"; +import { useGetMentorList } from "@/apis/mentor"; const MentorFindSection = () => { const { listRef, selectedTab, handleSelectTab } = useSelectedTab(); diff --git a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/ApplicantListSection/index.tsx b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/ApplicantListSection/index.tsx index 43574f37..65b4a5cd 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/ApplicantListSection/index.tsx +++ b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/ApplicantListSection/index.tsx @@ -3,7 +3,7 @@ import useInfinityScroll from "@/utils/useInfinityScroll"; import MentorExpandChatCard from "@/components/mentor/MentorExpandChatCard"; import EmptySdwBCards from "@/components/ui/EmptySdwBCards"; -import useGetMentoringList from "@/api/mentor/client/useGetMentoringList"; +import { useGetMentoringList } from "@/apis/mentor"; const ApplicantListSection = () => { const { data: mentoringApplicantList = [], fetchNextPage, hasNextPage } = useGetMentoringList({ size: 6 }); diff --git a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/MyMentorSection/index.tsx b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/MyMentorSection/index.tsx index 7e752571..0e567923 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/MyMentorSection/index.tsx +++ b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/_ui/MyMentorSection/index.tsx @@ -2,10 +2,10 @@ import MentorCard from "@/components/mentor/MentorCard"; -import useGetMyMentorProfile from "@/api/mentor/client/useGetMentorMyProfile"; +import { useGetMentorMyProfile } from "@/apis/mentor"; const MyMentorSection = () => { - const { data: myMentorProfile } = useGetMyMentorProfile(); + const { data: myMentorProfile } = useGetMentorMyProfile(); if (!myMentorProfile) { return
멘토 프로필을 불러오는 중...
; diff --git a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/index.tsx b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/index.tsx index 11d3456c..b5bc1a78 100644 --- a/src/app/mentor/_ui/MentorClient/_ui/MentorPage/index.tsx +++ b/src/app/mentor/_ui/MentorClient/_ui/MentorPage/index.tsx @@ -12,7 +12,7 @@ import MyMentorSection from "./_ui/MyMentorSection"; import { MentorTab } from "@/types/mentor"; -import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; +import { useGetChatRooms } from "@/apis/chat"; import { IconDirectionRight } from "@/public/svgs/mentor"; const MentorPage = () => { diff --git a/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts b/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts index 05dc3c00..c0caf400 100644 --- a/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts +++ b/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts @@ -5,7 +5,7 @@ import useInfinityScroll from "@/utils/useInfinityScroll"; import { ChatMessage, ConnectionStatus } from "@/types/chat"; -import useGetChatHistories from "@/api/chat/clients/useGetChatHistories"; +import { useGetChatHistories } from "@/apis/chat"; import useConnectWebSocket from "@/lib/web-socket/useConnectWebSocket"; import { Client } from "@stomp/stompjs"; diff --git a/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/usePutChatReadHandler.ts b/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/usePutChatReadHandler.ts index 97b81334..f40124cc 100644 --- a/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/usePutChatReadHandler.ts +++ b/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/usePutChatReadHandler.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import usePutChatRead from "@/api/chat/clients/usePutChatRead"; +import { usePutChatRead } from "@/apis/chat"; const usePutChatReadHandler = (chatId: number) => { const { mutate: putChatRead } = usePutChatRead(); diff --git a/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx b/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx index ccd1191e..32dc58d1 100644 --- a/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx +++ b/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx @@ -18,7 +18,7 @@ import ChatMessageBox from "./_ui/ChatMessageBox"; import { ConnectionStatus } from "@/types/chat"; import { UserRole } from "@/types/mentor"; -import useGetPartnerInfo from "@/api/chat/clients/useGetPartnerInfo"; +import { useGetPartnerInfo } from "@/apis/chat"; import useAuthStore from "@/lib/zustand/useAuthStore"; interface ChatContentProps { diff --git a/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx b/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx index 60f21621..f76636a3 100644 --- a/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx +++ b/src/app/mentor/chat/[chatId]/_ui/ChatNavBar/index.tsx @@ -14,8 +14,8 @@ import ReportPanel from "../../../../../../components/ui/ReportPanel"; import { UserRole } from "@/types/mentor"; -import useGetPartnerInfo from "@/api/chat/clients/useGetPartnerInfo"; import { useGetMyInfo } from "@/apis/MyPage"; +import { useGetPartnerInfo } from "@/apis/chat"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { IconAlert, IconAlertSubC, IconDirectionRight, IconSetting } from "@/public/svgs/mentor"; diff --git a/src/app/mentor/chat/_ui/ChatPageClient/index.tsx b/src/app/mentor/chat/_ui/ChatPageClient/index.tsx index 683969f2..f703268f 100644 --- a/src/app/mentor/chat/_ui/ChatPageClient/index.tsx +++ b/src/app/mentor/chat/_ui/ChatPageClient/index.tsx @@ -8,8 +8,8 @@ import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { UserRole } from "@/types/mentor"; -import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; import { useGetMyInfo } from "@/apis/MyPage"; +import { useGetChatRooms } from "@/apis/chat"; import { IconSearchBlue, IconSolidConnentionLogo } from "@/public/svgs/mentor"; const ChatPageClient = () => { diff --git a/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts b/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts index c8f1afe5..288c7128 100644 --- a/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts +++ b/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts @@ -1,6 +1,6 @@ import { MentoModifyFormData } from "../_lib/mentoModifyScehma"; -import usePutMyMentorProfile, { PutMyMentorProfileRequest } from "@/api/mentor/client/usePutMyMentorProfile"; +import { usePutMyMentorProfile, PutMyMentorProfileRequest } from "@/apis/mentor"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconModify } from "@/public/svgs/mentor"; diff --git a/src/app/mentor/modify/_ui/ModifyContent/_ui/ArticlePanel/_hooks/useDropDownHandler.ts b/src/app/mentor/modify/_ui/ModifyContent/_ui/ArticlePanel/_hooks/useDropDownHandler.ts index ec38a127..8bc61084 100644 --- a/src/app/mentor/modify/_ui/ModifyContent/_ui/ArticlePanel/_hooks/useDropDownHandler.ts +++ b/src/app/mentor/modify/_ui/ModifyContent/_ui/ArticlePanel/_hooks/useDropDownHandler.ts @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { ArticleDropdownType } from "@/types/news"; -import useDeleteArticle from "@/api/news/client/useDeleteArticle"; +import { useDeleteArticle } from "@/apis/news"; interface UseDeleteDropDownHandlerProps { articleId: number; diff --git a/src/app/mentor/modify/_ui/ModifyContent/index.tsx b/src/app/mentor/modify/_ui/ModifyContent/index.tsx index 1e6ddcb2..78bb2dda 100644 --- a/src/app/mentor/modify/_ui/ModifyContent/index.tsx +++ b/src/app/mentor/modify/_ui/ModifyContent/index.tsx @@ -12,12 +12,12 @@ import AddArticleCard from "./_ui/AddArticleCard"; import ArticlePanel from "./_ui/ArticlePanel"; import ChannelBox from "./_ui/ChannelBox"; -import useGetMyMentorProfile from "@/api/mentor/client/useGetMentorMyProfile"; -import useGetArticleList from "@/api/news/client/useGetArticleList"; +import { useGetMentorMyProfile } from "@/apis/mentor"; +import { useGetArticleList } from "@/apis/news"; import { IconUserPrimaryColor } from "@/public/svgs/mentor"; const ModifyContent = () => { - const { data: myMentorProfile = null } = useGetMyMentorProfile(); + const { data: myMentorProfile = null } = useGetMentorMyProfile(); const { data: articleList = [] } = useGetArticleList(myMentorProfile?.id || 0); const method = useModifyHookForm(myMentorProfile); diff --git a/src/app/mentor/waiting/_ui/WaitingContent/index.tsx b/src/app/mentor/waiting/_ui/WaitingContent/index.tsx index 924985d8..a48a8356 100644 --- a/src/app/mentor/waiting/_ui/WaitingContent/index.tsx +++ b/src/app/mentor/waiting/_ui/WaitingContent/index.tsx @@ -8,7 +8,7 @@ import MentorWaitingListBox from "./_ui/MentorWaitingListBox"; import { VerifyStatus } from "@/types/mentee"; -import useGetApplyMentoringList from "@/api/mentee/client/useGetApplyMentoringList"; +import { useGetApplyMentoringList } from "@/apis/mentor"; const DEFAULT_VISIBLE_ITEMS = 2; diff --git a/src/app/my/_ui/MyProfileContent/index.tsx b/src/app/my/_ui/MyProfileContent/index.tsx index 810e36a3..cacbc1bc 100644 --- a/src/app/my/_ui/MyProfileContent/index.tsx +++ b/src/app/my/_ui/MyProfileContent/index.tsx @@ -8,8 +8,8 @@ import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { UserRole } from "@/types/mentor"; -import { useGetMyInfo } from "@/apis/MyPage"; import { useDeleteUserAccount, usePostLogout } from "@/apis/Auth"; +import { useGetMyInfo } from "@/apis/MyPage"; import { toast } from "@/lib/zustand/useToastStore"; import { IconLikeFill } from "@/public/svgs/mentor"; import { diff --git a/src/app/my/apply-mentor/page.tsx b/src/app/my/apply-mentor/page.tsx index 079bf986..368f6714 100644 --- a/src/app/my/apply-mentor/page.tsx +++ b/src/app/my/apply-mentor/page.tsx @@ -13,7 +13,7 @@ import StudyStatusScreen from "./_components/StudyStatusScreen"; import UniversityScreen from "./_components/UniversityScreen"; import { MentorApplicationFormData, mentorApplicationSchema } from "./_lib/schema"; -import usePostMentorApplication from "@/api/mentor/client/usePostMentorApplication"; +import { usePostMentorApplication } from "@/apis/mentor"; import { toast } from "@/lib/zustand/useToastStore"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/src/app/my/match/_ui/MatchContent/index.tsx b/src/app/my/match/_ui/MatchContent/index.tsx index f70c2065..b34f52d5 100644 --- a/src/app/my/match/_ui/MatchContent/index.tsx +++ b/src/app/my/match/_ui/MatchContent/index.tsx @@ -7,8 +7,8 @@ import MentorChatCard from "@/components/mentor/MentorChatCard"; import { UserRole } from "@/types/mentor"; -import useGetChatRooms from "@/api/chat/clients/useGetChatRooms"; import { useGetMyInfo } from "@/apis/MyPage"; +import { useGetChatRooms } from "@/apis/chat"; const MatchContent = () => { const { data: myInfo = {} } = useGetMyInfo(); diff --git a/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts b/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts index 08f38094..732ddbb3 100644 --- a/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts +++ b/src/app/my/modify/_ui/ModifyContent/_hooks/useModifyUserHookform.ts @@ -3,7 +3,7 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; -import { useGetMyInfo, usePatchMyInfo, type MyInfoResponse } from "@/apis/MyPage"; +import { type MyInfoResponse, useGetMyInfo, usePatchMyInfo } from "@/apis/MyPage"; import { zodResolver } from "@hookform/resolvers/zod"; // Zod 스키마 정의 - 닉네임과 이미지 diff --git a/src/app/university/application/ScorePageContent.tsx b/src/app/university/application/ScorePageContent.tsx index b729129e..0991f75d 100644 --- a/src/app/university/application/ScorePageContent.tsx +++ b/src/app/university/application/ScorePageContent.tsx @@ -16,7 +16,7 @@ import { REGIONS_KO } from "@/constants/university"; import { ScoreSheet as ScoreSheetType } from "@/types/application"; import { RegionKo } from "@/types/university"; -import useGetApplicationsList from "@/api/applications/client/useGetApplicationsList"; +import { useGetApplicationsList } from "@/apis/applications"; import { toast } from "@/lib/zustand/useToastStore"; const PREFERENCE_CHOICE: ("1순위" | "2순위" | "3순위")[] = ["1순위", "2순위", "3순위"]; diff --git a/src/app/university/score/ScoreScreen.tsx b/src/app/university/score/ScoreScreen.tsx index 940311f0..7a667222 100644 --- a/src/app/university/score/ScoreScreen.tsx +++ b/src/app/university/score/ScoreScreen.tsx @@ -10,8 +10,7 @@ import ScoreCard from "./ScoreCard"; import { languageTestMapping } from "@/types/score"; -import useGetMyGpaScore from "@/api/score/client/useGetMyGpaScore"; -import useGetMyLanguageTestScore from "@/api/score/client/useGetMyLanguageTestScore"; +import { useGetMyGpaScore, useGetMyLanguageTestScore } from "@/apis/Scores"; const ScoreScreen = () => { const router = useRouter(); diff --git a/src/app/university/score/submit/gpa/GpaSubmitForm.tsx b/src/app/university/score/submit/gpa/GpaSubmitForm.tsx index 478a5ad7..47456800 100644 --- a/src/app/university/score/submit/gpa/GpaSubmitForm.tsx +++ b/src/app/university/score/submit/gpa/GpaSubmitForm.tsx @@ -15,7 +15,7 @@ import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage"; // CustomDropdown 경로 확인 필요 import { GpaFormData, gpaSchema } from "./_lib/schema"; -import { usePostGpaScore } from "@/api/score/client/usePostGpaScore"; +import { usePostGpaScore } from "@/apis/Scores"; import CustomDropdown from "@/app/university/CustomDropdown"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx b/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx index 903e9606..58dafe5c 100644 --- a/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx +++ b/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx @@ -17,7 +17,7 @@ import { LanguageTestFormData, languageTestSchema } from "./_lib/schema"; import { LanguageTestEnum } from "@/types/score"; -import { usePostLanguageTestScore } from "@/api/score/client/usePostLanguageTestScore"; +import { usePostLanguageTestScore } from "@/apis/Scores"; import CustomDropdown from "@/app/university/CustomDropdown"; import { toast } from "@/lib/zustand/useToastStore"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/src/components/login/signup/SignupSurvey.tsx b/src/components/login/signup/SignupSurvey.tsx index 25aa787b..8f98196b 100644 --- a/src/components/login/signup/SignupSurvey.tsx +++ b/src/components/login/signup/SignupSurvey.tsx @@ -14,7 +14,7 @@ import { PreparationStatus, SignUpRequest } from "@/types/auth"; import { RegionKo } from "@/types/university"; import { usePostSignUp } from "@/apis/Auth"; -import useUploadProfileImagePublic from "@/api/file/client/useUploadProfileImagePublic"; +import { useUploadProfileImagePublic } from "@/apis/image-upload"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; diff --git a/src/components/mentor/ArticleBottomSheetModal/hooks/useArticleSchema.ts b/src/components/mentor/ArticleBottomSheetModal/hooks/useArticleSchema.ts index fd545d95..3c877bf6 100644 --- a/src/components/mentor/ArticleBottomSheetModal/hooks/useArticleSchema.ts +++ b/src/components/mentor/ArticleBottomSheetModal/hooks/useArticleSchema.ts @@ -6,9 +6,8 @@ import { convertUploadedImageUrl } from "@/utils/fileUtils"; import { InitialData } from ".."; import { ArticleFormData, articleSchema } from "../lib/schema"; -import useGetMentorMyProfile from "@/api/mentor/client/useGetMentorMyProfile"; -import usePostAddArticle from "@/api/news/client/usePostAddArticle"; -import usePutModifyArticle from "@/api/news/client/usePutModifyArticle"; +import { useGetMentorMyProfile } from "@/apis/mentor"; +import { usePostAddArticle, usePutModifyArticle } from "@/apis/news"; import { zodResolver } from "@hookform/resolvers/zod"; interface UseArticleSchemaProps { diff --git a/src/components/mentor/MentorApplyCountContent/index.tsx b/src/components/mentor/MentorApplyCountContent/index.tsx index 34f424ca..f8a668d3 100644 --- a/src/components/mentor/MentorApplyCountContent/index.tsx +++ b/src/components/mentor/MentorApplyCountContent/index.tsx @@ -7,7 +7,7 @@ import { tokenParse } from "@/utils/jwtUtils"; import { UserRole } from "@/types/mentor"; -import useGetMentoringUncheckedCount from "@/api/mentor/client/useGetMentoringUncheckedCount"; +import { useGetMentoringUncheckedCount } from "@/apis/mentor"; import useAuthStore from "@/lib/zustand/useAuthStore"; const MentorApplyCountContent = () => { diff --git a/src/components/mentor/MentorCard/hooks/usePostApplyMentorHandler.ts b/src/components/mentor/MentorCard/hooks/usePostApplyMentorHandler.ts index 887cd9c0..bc0723ae 100644 --- a/src/components/mentor/MentorCard/hooks/usePostApplyMentorHandler.ts +++ b/src/components/mentor/MentorCard/hooks/usePostApplyMentorHandler.ts @@ -1,6 +1,6 @@ import { useRouter } from "next/navigation"; -import usePostApplyMentoring from "@/api/mentee/client/usePostApplyMentoring"; +import { usePostApplyMentoring } from "@/apis/mentor"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconCheck } from "@/public/svgs/mentor"; diff --git a/src/components/mentor/MentorExpandChatCard/hooks/useExpandCardClickHandler.ts b/src/components/mentor/MentorExpandChatCard/hooks/useExpandCardClickHandler.ts index e7dc7511..ed4bebaf 100644 --- a/src/components/mentor/MentorExpandChatCard/hooks/useExpandCardClickHandler.ts +++ b/src/components/mentor/MentorExpandChatCard/hooks/useExpandCardClickHandler.ts @@ -4,8 +4,7 @@ import { tokenParse } from "@/utils/jwtUtils"; import { UserRole } from "@/types/mentor"; -import usePatchMenteeCheckMentorings from "@/api/mentee/client/usePatchMenteeCheckMentorings"; -import usePatchMentorCheckMentorings from "@/api/mentor/client/usePatchMentorCheckMentorings"; +import { usePatchMenteeCheckMentorings, usePatchMentorCheckMentorings } from "@/apis/mentor"; import useAuthStore from "@/lib/zustand/useAuthStore"; interface UseExpandCardClickHandlerReturn { diff --git a/src/components/mentor/MentorExpandChatCard/hooks/usePatchApprovalStatusHandler.ts b/src/components/mentor/MentorExpandChatCard/hooks/usePatchApprovalStatusHandler.ts index 3ba420b6..a989f85e 100644 --- a/src/components/mentor/MentorExpandChatCard/hooks/usePatchApprovalStatusHandler.ts +++ b/src/components/mentor/MentorExpandChatCard/hooks/usePatchApprovalStatusHandler.ts @@ -1,6 +1,6 @@ import { MentoringApprovalStatus } from "@/types/mentor"; -import usePatchApprovalStatus from "@/api/mentor/client/usePatchApprovalStatus"; +import { usePatchApprovalStatus } from "@/apis/mentor"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconUnSmile } from "@/public/svgs/mentor"; diff --git a/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.ts b/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.ts index b85412a3..c3c734cf 100644 --- a/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.ts +++ b/src/components/ui/ReportPanel/_hooks/useSelectReportHandler.ts @@ -3,7 +3,7 @@ import { useState } from "react"; import { reportReasons } from "@/constants/report"; import { ReportType } from "@/types/reports"; -import usePostReports from "@/api/reports/client/usePostReport"; +import { usePostReports } from "@/apis/reports"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconReport } from "@/public/svgs/mentor"; From df0cc402d75d9123be3ae3ebac5f7372cfee16d9 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 22:58:33 +0900 Subject: [PATCH 05/11] style: apply linter formatting to apis and components --- src/apis/Scores/api.ts | 6 ++- src/apis/Scores/getGpaList.ts | 9 ++-- src/apis/Scores/getLanguageTestList.ts | 9 ++-- src/apis/Scores/index.ts | 18 +++---- src/apis/Scores/postCreateGpa.ts | 8 +-- src/apis/Scores/postCreateLanguageTest.ts | 8 +-- src/apis/applications/api.ts | 4 +- src/apis/applications/getApplicants.ts | 15 +++--- src/apis/applications/index.ts | 10 ++-- .../applications/postSubmitApplication.ts | 8 +-- src/apis/chat/getChatPartner.ts | 6 ++- src/apis/chat/index.ts | 12 ++--- src/apis/chat/putReadChatRoom.ts | 6 ++- src/apis/community/getPostList.ts | 7 ++- src/apis/community/server.ts | 1 + src/apis/image-upload/api.ts | 31 ++++++----- src/apis/image-upload/index.ts | 18 +++---- .../postUploadProfileImageBeforeSignup.ts | 7 ++- src/apis/mentor/api.ts | 12 ++--- src/apis/mentor/getAppliedMentorings.ts | 14 ++--- src/apis/mentor/getMentorDetail.ts | 6 ++- src/apis/mentor/getMentorList.ts | 6 ++- src/apis/mentor/getMyMentorPage.ts | 6 ++- src/apis/mentor/getReceivedMentorings.ts | 6 ++- .../mentor/getUnconfirmedMentoringCount.ts | 6 ++- src/apis/mentor/index.ts | 38 ++++++------- src/apis/mentor/patchConfirmMentoring.ts | 6 ++- src/apis/mentor/patchMenteeCheckMentorings.ts | 4 +- src/apis/mentor/patchMentoringStatus.ts | 15 ++++-- src/apis/mentor/postApplyMentoring.ts | 8 +-- src/apis/mentor/postMentorApplication.ts | 6 ++- src/apis/mentor/putUpdateMyMentorPage.ts | 6 ++- src/apis/news/api.ts | 7 ++- src/apis/news/deleteLikeNews.ts | 9 ++-- src/apis/news/deleteNews.ts | 9 ++-- src/apis/news/getNewsList.ts | 9 ++-- src/apis/news/index.ts | 24 ++++----- src/apis/news/postCreateNews.ts | 9 ++-- src/apis/news/postLikeNews.ts | 9 ++-- src/apis/news/putUpdateNews.ts | 9 ++-- src/apis/reports/api.ts | 4 +- src/apis/reports/index.ts | 6 +-- src/apis/reports/postReport.ts | 9 ++-- src/apis/universities/api.ts | 54 +++++++++---------- src/apis/universities/deleteWish.ts | 11 ++-- .../getRecommendedUniversities.ts | 9 ++-- src/apis/universities/getSearchFilter.ts | 9 ++-- src/apis/universities/getSearchText.ts | 14 ++--- src/apis/universities/getUniversityDetail.ts | 9 ++-- src/apis/universities/getWishList.ts | 9 ++-- src/apis/universities/index.ts | 20 +++---- src/apis/universities/postAddWish.ts | 11 ++-- src/apis/universities/server/index.ts | 6 +-- src/app/(home)/page.tsx | 2 +- .../[id]/_ui/MentorDetialContent/index.tsx | 2 +- .../_hooks/usePutMyMentorProfileHandler.ts | 2 +- src/app/university/SearchResultsContent.tsx | 2 +- src/app/university/[id]/page.tsx | 2 +- 58 files changed, 337 insertions(+), 251 deletions(-) diff --git a/src/apis/Scores/api.ts b/src/apis/Scores/api.ts index 645e9a71..0ccd1d05 100644 --- a/src/apis/Scores/api.ts +++ b/src/apis/Scores/api.ts @@ -1,6 +1,8 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; -import { GpaScore, LanguageTestScore, LanguageTestEnum } from "@/types/score"; + +import { GpaScore, LanguageTestEnum, LanguageTestScore } from "@/types/score"; // ====== Query Keys ====== export const ScoresQueryKeys = { @@ -76,4 +78,4 @@ export const scoresApi = { formData.append("file", request.file); return axiosInstance.post("/scores/language-tests", formData); }, -}; \ No newline at end of file +}; diff --git a/src/apis/Scores/getGpaList.ts b/src/apis/Scores/getGpaList.ts index c0e06949..a2344ba2 100644 --- a/src/apis/Scores/getGpaList.ts +++ b/src/apis/Scores/getGpaList.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { scoresApi, ScoresQueryKeys } from "./api"; + +import { ScoresQueryKeys, scoresApi } from "./api"; + import { GpaScore } from "@/types/score"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 내 학점 점수 조회 훅 */ @@ -15,4 +18,4 @@ const useGetMyGpaScore = () => { }); }; -export default useGetMyGpaScore; \ No newline at end of file +export default useGetMyGpaScore; diff --git a/src/apis/Scores/getLanguageTestList.ts b/src/apis/Scores/getLanguageTestList.ts index d04c7d60..642ec2b2 100644 --- a/src/apis/Scores/getLanguageTestList.ts +++ b/src/apis/Scores/getLanguageTestList.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { scoresApi, ScoresQueryKeys } from "./api"; + +import { ScoresQueryKeys, scoresApi } from "./api"; + import { LanguageTestScore } from "@/types/score"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 내 어학 점수 조회 훅 */ @@ -15,4 +18,4 @@ const useGetMyLanguageTestScore = () => { }); }; -export default useGetMyLanguageTestScore; \ No newline at end of file +export default useGetMyLanguageTestScore; diff --git a/src/apis/Scores/index.ts b/src/apis/Scores/index.ts index 66124ea4..82bc201a 100644 --- a/src/apis/Scores/index.ts +++ b/src/apis/Scores/index.ts @@ -1,12 +1,12 @@ -export { scoresApi, ScoresQueryKeys } from './api'; -export type { - UseMyGpaScoreResponse, +export { scoresApi, ScoresQueryKeys } from "./api"; +export type { + UseMyGpaScoreResponse, UseGetMyLanguageTestScoreResponse, UsePostGpaScoreRequest, - UsePostLanguageTestScoreRequest -} from './api'; + UsePostLanguageTestScoreRequest, +} from "./api"; -export { default as useGetMyGpaScore } from './getGpaList'; -export { default as useGetMyLanguageTestScore } from './getLanguageTestList'; -export { default as usePostGpaScore } from './postCreateGpa'; -export { default as usePostLanguageTestScore } from './postCreateLanguageTest'; +export { default as useGetMyGpaScore } from "./getGpaList"; +export { default as useGetMyLanguageTestScore } from "./getLanguageTestList"; +export { default as usePostGpaScore } from "./postCreateGpa"; +export { default as usePostLanguageTestScore } from "./postCreateLanguageTest"; diff --git a/src/apis/Scores/postCreateGpa.ts b/src/apis/Scores/postCreateGpa.ts index bd692050..a3252781 100644 --- a/src/apis/Scores/postCreateGpa.ts +++ b/src/apis/Scores/postCreateGpa.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { scoresApi, ScoresQueryKeys, UsePostGpaScoreRequest } from "./api"; + +import { ScoresQueryKeys, UsePostGpaScoreRequest, scoresApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 학점 점수 제출 훅 @@ -24,4 +26,4 @@ export const usePostGpaScore = () => { }); }; -export default usePostGpaScore; \ No newline at end of file +export default usePostGpaScore; diff --git a/src/apis/Scores/postCreateLanguageTest.ts b/src/apis/Scores/postCreateLanguageTest.ts index d3917dec..bc8527ff 100644 --- a/src/apis/Scores/postCreateLanguageTest.ts +++ b/src/apis/Scores/postCreateLanguageTest.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { scoresApi, ScoresQueryKeys, UsePostLanguageTestScoreRequest } from "./api"; + +import { ScoresQueryKeys, UsePostLanguageTestScoreRequest, scoresApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 어학 점수 제출 훅 @@ -24,4 +26,4 @@ export const usePostLanguageTestScore = () => { }); }; -export default usePostLanguageTestScore; \ No newline at end of file +export default usePostLanguageTestScore; diff --git a/src/apis/applications/api.ts b/src/apis/applications/api.ts index 2f61ddf5..c8b0917a 100644 --- a/src/apis/applications/api.ts +++ b/src/apis/applications/api.ts @@ -1,5 +1,7 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; + import { ApplicationListResponse } from "@/types/application"; // ====== Query Keys ====== @@ -39,4 +41,4 @@ export const applicationsApi = { ): Promise> => { return axiosInstance.post("/applications", request); }, -}; \ No newline at end of file +}; diff --git a/src/apis/applications/getApplicants.ts b/src/apis/applications/getApplicants.ts index 5b24eae0..6523baa8 100644 --- a/src/apis/applications/getApplicants.ts +++ b/src/apis/applications/getApplicants.ts @@ -1,14 +1,13 @@ import { AxiosError, AxiosResponse } from "axios"; -import { UseQueryOptions, UseQueryResult, useQuery } from "@tanstack/react-query"; -import { applicationsApi, ApplicationsQueryKeys } from "./api"; + +import { ApplicationsQueryKeys, applicationsApi } from "./api"; + import { ApplicationListResponse } from "@/types/application"; +import { UseQueryOptions, UseQueryResult, useQuery } from "@tanstack/react-query"; + type UseGetApplicationsListOptions = Omit< - UseQueryOptions< - AxiosResponse, - AxiosError<{ message: string }>, - ApplicationListResponse - >, + UseQueryOptions, AxiosError<{ message: string }>, ApplicationListResponse>, "queryKey" | "queryFn" >; @@ -27,4 +26,4 @@ const useGetApplicationsList = ( }); }; -export default useGetApplicationsList; \ No newline at end of file +export default useGetApplicationsList; diff --git a/src/apis/applications/index.ts b/src/apis/applications/index.ts index a70a656f..a618c45e 100644 --- a/src/apis/applications/index.ts +++ b/src/apis/applications/index.ts @@ -1,5 +1,5 @@ -export { applicationsApi, ApplicationsQueryKeys } from './api'; -export type { UseSubmitApplicationResponse, UseSubmitApplicationRequest } from './api'; -export { default as useGetApplicationsList } from './getApplicants'; -export { default as useGetCompetitors } from './getCompetitors'; -export { default as usePostSubmitApplication } from './postSubmitApplication'; +export { applicationsApi, ApplicationsQueryKeys } from "./api"; +export type { UseSubmitApplicationResponse, UseSubmitApplicationRequest } from "./api"; +export { default as useGetApplicationsList } from "./getApplicants"; +export { default as useGetCompetitors } from "./getCompetitors"; +export { default as usePostSubmitApplication } from "./postSubmitApplication"; diff --git a/src/apis/applications/postSubmitApplication.ts b/src/apis/applications/postSubmitApplication.ts index a5ccd125..45c47bd5 100644 --- a/src/apis/applications/postSubmitApplication.ts +++ b/src/apis/applications/postSubmitApplication.ts @@ -1,7 +1,9 @@ import { AxiosError, AxiosResponse } from "axios"; -import { UseMutationOptions, UseMutationResult, useMutation } from "@tanstack/react-query"; -import { applicationsApi, UseSubmitApplicationResponse, UseSubmitApplicationRequest } from "./api"; + +import { UseSubmitApplicationRequest, UseSubmitApplicationResponse, applicationsApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { UseMutationOptions, UseMutationResult, useMutation } from "@tanstack/react-query"; /** * @description 지원 제출 훅 @@ -33,4 +35,4 @@ const usePostSubmitApplication = ( }); }; -export default usePostSubmitApplication; \ No newline at end of file +export default usePostSubmitApplication; diff --git a/src/apis/chat/getChatPartner.ts b/src/apis/chat/getChatPartner.ts index b6143abc..0f871aa8 100644 --- a/src/apis/chat/getChatPartner.ts +++ b/src/apis/chat/getChatPartner.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { ChatPartner, ChatQueryKeys, chatApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { chatApi, ChatQueryKeys, ChatPartner } from "./api"; /** * @description 채팅 상대방 정보를 가져오는 훅 @@ -14,4 +16,4 @@ const useGetPartnerInfo = (roomId: number) => { }); }; -export default useGetPartnerInfo; \ No newline at end of file +export default useGetPartnerInfo; diff --git a/src/apis/chat/index.ts b/src/apis/chat/index.ts index 6519352b..8774747a 100644 --- a/src/apis/chat/index.ts +++ b/src/apis/chat/index.ts @@ -1,6 +1,6 @@ -export { chatApi, ChatQueryKeys } from './api'; -export type { ChatHistoriesResponse, ChatRoomListResponse, ChatMessage, ChatRoom, ChatPartner } from './api'; -export { default as useGetChatHistories } from './getChatMessages'; -export { default as useGetPartnerInfo } from './getChatPartner'; -export { default as useGetChatRooms } from './getChatRooms'; -export { default as usePutChatRead } from './putReadChatRoom'; +export { chatApi, ChatQueryKeys } from "./api"; +export type { ChatHistoriesResponse, ChatRoomListResponse, ChatMessage, ChatRoom, ChatPartner } from "./api"; +export { default as useGetChatHistories } from "./getChatMessages"; +export { default as useGetPartnerInfo } from "./getChatPartner"; +export { default as useGetChatRooms } from "./getChatRooms"; +export { default as usePutChatRead } from "./putReadChatRoom"; diff --git a/src/apis/chat/putReadChatRoom.ts b/src/apis/chat/putReadChatRoom.ts index b72805da..6d955204 100644 --- a/src/apis/chat/putReadChatRoom.ts +++ b/src/apis/chat/putReadChatRoom.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { ChatQueryKeys, chatApi } from "./api"; + import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { chatApi, ChatQueryKeys } from "./api"; /** * @description 채팅방 읽음 처리 훅 @@ -20,4 +22,4 @@ const usePutChatRead = () => { }); }; -export default usePutChatRead; \ No newline at end of file +export default usePutChatRead; diff --git a/src/apis/community/getPostList.ts b/src/apis/community/getPostList.ts index 3715ae14..ca8341f7 100644 --- a/src/apis/community/getPostList.ts +++ b/src/apis/community/getPostList.ts @@ -1,8 +1,11 @@ import { AxiosResponse } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { communityApi, CommunityQueryKeys } from "./api"; + +import { CommunityQueryKeys, communityApi } from "./api"; + import { ListPost } from "@/types/community"; +import { useQuery } from "@tanstack/react-query"; + interface UseGetPostListProps { boardCode: string; category?: string | null; diff --git a/src/apis/community/server.ts b/src/apis/community/server.ts index e311c887..7e476a6d 100644 --- a/src/apis/community/server.ts +++ b/src/apis/community/server.ts @@ -1,4 +1,5 @@ import serverFetch, { ServerFetchResult } from "@/utils/serverFetchUtil"; + import { ListPost } from "@/types/community"; interface GetPostListParams { diff --git a/src/apis/image-upload/api.ts b/src/apis/image-upload/api.ts index 8a694299..f93f1490 100644 --- a/src/apis/image-upload/api.ts +++ b/src/apis/image-upload/api.ts @@ -1,5 +1,7 @@ import { AxiosResponse } from "axios"; + import { axiosInstance, publicAxiosInstance } from "@/utils/axiosInstance"; + import { FileResponse } from "@/types/file"; // ====== Types ====== @@ -25,7 +27,8 @@ export const imageUploadApi = { */ postSlackNotification: async (params: { data?: SlackNotificationRequest }): Promise => { const res = await axiosInstance.post( - `https://hooks.slack.com/services/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`, params?.data + `https://hooks.slack.com/services/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`, + params?.data, ); return res.data; }, @@ -36,9 +39,9 @@ export const imageUploadApi = { postUploadLanguageTestReport: async (file: File): Promise => { const formData = new FormData(); formData.append("file", file); - const res = await axiosInstance.post( - `/file/language-test`, formData, { headers: { "Content-Type": "multipart/form-data" } } - ); + const res = await axiosInstance.post(`/file/language-test`, formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); return res.data; }, @@ -48,9 +51,9 @@ export const imageUploadApi = { postUploadProfileImage: async (file: File): Promise => { const formData = new FormData(); formData.append("file", file); - const res = await axiosInstance.post( - `/file/profile/post`, formData, { headers: { "Content-Type": "multipart/form-data" } } - ); + const res = await axiosInstance.post(`/file/profile/post`, formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); return res.data; }, @@ -60,9 +63,9 @@ export const imageUploadApi = { postUploadProfileImageBeforeSignup: async (file: File): Promise => { const formData = new FormData(); formData.append("file", file); - const response: AxiosResponse = await publicAxiosInstance.post( - "/file/profile/pre", formData, { headers: { "Content-Type": "multipart/form-data" } } - ); + const response: AxiosResponse = await publicAxiosInstance.post("/file/profile/pre", formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); return response.data; }, @@ -72,9 +75,9 @@ export const imageUploadApi = { postUploadGpaReport: async (file: File): Promise => { const formData = new FormData(); formData.append("file", file); - const res = await axiosInstance.post( - `/file/gpa`, formData, { headers: { "Content-Type": "multipart/form-data" } } - ); + const res = await axiosInstance.post(`/file/gpa`, formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); return res.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/image-upload/index.ts b/src/apis/image-upload/index.ts index 9a7dad88..83a2df4e 100644 --- a/src/apis/image-upload/index.ts +++ b/src/apis/image-upload/index.ts @@ -1,12 +1,8 @@ -export { imageUploadApi } from './api'; -export type { - UploadLanguageTestReportResponse, - UploadProfileImageResponse, - UploadGpaReportResponse -} from './api'; +export { imageUploadApi } from "./api"; +export type { UploadLanguageTestReportResponse, UploadProfileImageResponse, UploadGpaReportResponse } from "./api"; -export { default as useSlackNotification } from './postSlackNotification'; -export { default as useUploadGpaReport } from './postUploadGpaReport'; -export { default as useUploadLanguageTestReport } from './postUploadLanguageTestReport'; -export { default as useUploadProfileImage } from './postUploadProfileImage'; -export { default as useUploadProfileImagePublic } from './postUploadProfileImageBeforeSignup'; +export { default as useSlackNotification } from "./postSlackNotification"; +export { default as useUploadGpaReport } from "./postUploadGpaReport"; +export { default as useUploadLanguageTestReport } from "./postUploadLanguageTestReport"; +export { default as useUploadProfileImage } from "./postUploadProfileImage"; +export { default as useUploadProfileImagePublic } from "./postUploadProfileImageBeforeSignup"; diff --git a/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts index d2ddb6d5..3e7bfc30 100644 --- a/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts +++ b/src/apis/image-upload/postUploadProfileImageBeforeSignup.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; + import { imageUploadApi } from "./api"; + import { FileResponse } from "@/types/file"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; /** * @description 프로필 이미지 업로드를 위한 useMutation 커스텀 훅 (회원가입 전 공개 API) @@ -17,4 +20,4 @@ const useUploadProfileImagePublic = () => { }); }; -export default useUploadProfileImagePublic; \ No newline at end of file +export default useUploadProfileImagePublic; diff --git a/src/apis/mentor/api.ts b/src/apis/mentor/api.ts index e2ce44a8..0e312655 100644 --- a/src/apis/mentor/api.ts +++ b/src/apis/mentor/api.ts @@ -1,7 +1,9 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; -import { MentorCardPreview, MentorCardDetail, MentoringItem, MentoringApprovalStatus } from "@/types/mentor"; + import { MentoringListItem, VerifyStatus } from "@/types/mentee"; +import { MentorCardDetail, MentorCardPreview, MentoringApprovalStatus, MentoringItem } from "@/types/mentor"; // QueryKeys for mentor domain export const MentorQueryKeys = { @@ -141,7 +143,7 @@ export const mentorApi = { getApplyMentoringList: async ( verifyStatus: VerifyStatus, page: number, - size: number = MENTEE_OFFSET + size: number = MENTEE_OFFSET, ): Promise => { const res = await axiosInstance.get( `/mentee/mentorings?verify-status=${verifyStatus}&size=${size}&page=${page}`, @@ -161,9 +163,7 @@ export const mentorApi = { // === Mentors (멘토 목록) APIs === getMentorList: async (region: string, page: number, size: number = MENTORS_OFFSET): Promise => { - const res = await axiosInstance.get( - `/mentors?region=${region}&page=${page}&size=${size}`, - ); + const res = await axiosInstance.get(`/mentors?region=${region}&page=${page}&size=${size}`); return res.data; }, @@ -171,4 +171,4 @@ export const mentorApi = { const res = await axiosInstance.get(`/mentors/${mentorId}`); return res.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/mentor/getAppliedMentorings.ts b/src/apis/mentor/getAppliedMentorings.ts index 6c58c95d..2d6d516f 100644 --- a/src/apis/mentor/getAppliedMentorings.ts +++ b/src/apis/mentor/getAppliedMentorings.ts @@ -1,19 +1,15 @@ import { AxiosError } from "axios"; + +import { ApplyMentoringListResponse, MentorQueryKeys, MentoringListItem, VerifyStatus, mentorApi } from "./api"; + import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import type { QueryFunctionContext } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, ApplyMentoringListResponse, MentoringListItem, VerifyStatus } from "./api"; /** * @description 신청한 멘토링 목록 조회 훅 (무한 스크롤) */ const useGetApplyMentoringList = (verifyStatus: VerifyStatus) => { - return useInfiniteQuery< - ApplyMentoringListResponse, - AxiosError, - MentoringListItem[], - [string, VerifyStatus], - number - >({ + return useInfiniteQuery({ queryKey: [MentorQueryKeys.applyMentoringList, verifyStatus], queryFn: ({ pageParam = 0 }) => mentorApi.getApplyMentoringList(verifyStatus, pageParam), initialPageParam: 0, @@ -39,4 +35,4 @@ export const usePrefetchApplyMentoringList = () => { return { prefetchList }; }; -export default useGetApplyMentoringList; \ No newline at end of file +export default useGetApplyMentoringList; diff --git a/src/apis/mentor/getMentorDetail.ts b/src/apis/mentor/getMentorDetail.ts index 42dd8f97..6a689866 100644 --- a/src/apis/mentor/getMentorDetail.ts +++ b/src/apis/mentor/getMentorDetail.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorCardDetail, MentorQueryKeys, mentorApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, MentorCardDetail } from "./api"; /** * @description 멘토 상세 조회 훅 @@ -14,4 +16,4 @@ const useGetMentorDetail = (mentorId: number | null) => { }); }; -export default useGetMentorDetail; \ No newline at end of file +export default useGetMentorDetail; diff --git a/src/apis/mentor/getMentorList.ts b/src/apis/mentor/getMentorList.ts index 08eedc1d..13f64f6c 100644 --- a/src/apis/mentor/getMentorList.ts +++ b/src/apis/mentor/getMentorList.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; + +import { MentorCardDetail, MentorListResponse, MentorQueryKeys, mentorApi } from "./api"; + import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query"; import type { QueryFunctionContext } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, MentorListResponse, MentorCardDetail } from "./api"; interface UseGetMentorListRequest { region?: string; @@ -37,4 +39,4 @@ export const usePrefetchMentorList = () => { return { prefetchMentorList }; }; -export default useGetMentorList; \ No newline at end of file +export default useGetMentorList; diff --git a/src/apis/mentor/getMyMentorPage.ts b/src/apis/mentor/getMyMentorPage.ts index 26232b21..09d2508c 100644 --- a/src/apis/mentor/getMyMentorPage.ts +++ b/src/apis/mentor/getMyMentorPage.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorCardPreview, MentorQueryKeys, mentorApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, MentorCardPreview } from "./api"; /** * @description 멘토 마이 프로필 조회 훅 @@ -13,4 +15,4 @@ const useGetMentorMyProfile = () => { }); }; -export default useGetMentorMyProfile; \ No newline at end of file +export default useGetMentorMyProfile; diff --git a/src/apis/mentor/getReceivedMentorings.ts b/src/apis/mentor/getReceivedMentorings.ts index 0f401940..a193248b 100644 --- a/src/apis/mentor/getReceivedMentorings.ts +++ b/src/apis/mentor/getReceivedMentorings.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorQueryKeys, MentoringItem, MentoringListResponse, mentorApi } from "./api"; + import { useInfiniteQuery } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, MentoringListResponse, MentoringItem } from "./api"; const OFFSET = 5; @@ -21,4 +23,4 @@ const useGetMentoringList = ({ size = OFFSET }: { size?: number } = {}) => { }); }; -export default useGetMentoringList; \ No newline at end of file +export default useGetMentoringList; diff --git a/src/apis/mentor/getUnconfirmedMentoringCount.ts b/src/apis/mentor/getUnconfirmedMentoringCount.ts index a5627999..b3fa51c5 100644 --- a/src/apis/mentor/getUnconfirmedMentoringCount.ts +++ b/src/apis/mentor/getUnconfirmedMentoringCount.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorQueryKeys, mentorApi } from "./api"; + import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys } from "./api"; /** * @description 미확인 멘토링 수 조회 훅 @@ -16,4 +18,4 @@ const useGetMentoringUncheckedCount = (isEnable: boolean) => { }); }; -export default useGetMentoringUncheckedCount; \ No newline at end of file +export default useGetMentoringUncheckedCount; diff --git a/src/apis/mentor/index.ts b/src/apis/mentor/index.ts index decbddc3..cd1e34f2 100644 --- a/src/apis/mentor/index.ts +++ b/src/apis/mentor/index.ts @@ -1,29 +1,29 @@ -export { mentorApi, MentorQueryKeys } from './api'; -export type { - MentorCardPreview, - MentorCardDetail, - MentoringItem, +export { mentorApi, MentorQueryKeys } from "./api"; +export type { + MentorCardPreview, + MentorCardDetail, + MentoringItem, MentoringApprovalStatus, MentoringListItem, VerifyStatus, PutMyMentorProfileRequest, - PostMentorApplicationRequest -} from './api'; + PostMentorApplicationRequest, +} from "./api"; // Mentor (멘토) hooks -export { default as useGetMentorMyProfile } from './getMyMentorPage'; -export { default as useGetMentoringList } from './getReceivedMentorings'; -export { default as useGetMentoringUncheckedCount } from './getUnconfirmedMentoringCount'; -export { default as usePatchApprovalStatus } from './patchMentoringStatus'; -export { default as usePatchMentorCheckMentorings } from './patchConfirmMentoring'; -export { default as usePostMentorApplication } from './postMentorApplication'; -export { default as usePutMyMentorProfile } from './putUpdateMyMentorPage'; +export { default as useGetMentorMyProfile } from "./getMyMentorPage"; +export { default as useGetMentoringList } from "./getReceivedMentorings"; +export { default as useGetMentoringUncheckedCount } from "./getUnconfirmedMentoringCount"; +export { default as usePatchApprovalStatus } from "./patchMentoringStatus"; +export { default as usePatchMentorCheckMentorings } from "./patchConfirmMentoring"; +export { default as usePostMentorApplication } from "./postMentorApplication"; +export { default as usePutMyMentorProfile } from "./putUpdateMyMentorPage"; // Mentee (멘티) hooks -export { default as useGetApplyMentoringList, usePrefetchApplyMentoringList } from './getAppliedMentorings'; -export { default as usePatchMenteeCheckMentorings } from './patchMenteeCheckMentorings'; -export { default as usePostApplyMentoring } from './postApplyMentoring'; +export { default as useGetApplyMentoringList, usePrefetchApplyMentoringList } from "./getAppliedMentorings"; +export { default as usePatchMenteeCheckMentorings } from "./patchMenteeCheckMentorings"; +export { default as usePostApplyMentoring } from "./postApplyMentoring"; // Mentors (멘토 목록) hooks -export { default as useGetMentorList, usePrefetchMentorList } from './getMentorList'; -export { default as useGetMentorDetail } from './getMentorDetail'; +export { default as useGetMentorList, usePrefetchMentorList } from "./getMentorList"; +export { default as useGetMentorDetail } from "./getMentorDetail"; diff --git a/src/apis/mentor/patchConfirmMentoring.ts b/src/apis/mentor/patchConfirmMentoring.ts index 38cf8042..f61c2875 100644 --- a/src/apis/mentor/patchConfirmMentoring.ts +++ b/src/apis/mentor/patchConfirmMentoring.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorQueryKeys, PatchCheckMentoringsRequest, PatchCheckMentoringsResponse, mentorApi } from "./api"; + import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, PatchCheckMentoringsRequest, PatchCheckMentoringsResponse } from "./api"; /** * @description 멘토 멘토링 확인 처리 훅 @@ -19,4 +21,4 @@ const usePatchMentorCheckMentorings = () => { }); }; -export default usePatchMentorCheckMentorings; \ No newline at end of file +export default usePatchMentorCheckMentorings; diff --git a/src/apis/mentor/patchMenteeCheckMentorings.ts b/src/apis/mentor/patchMenteeCheckMentorings.ts index ecb9a26c..4308b11c 100644 --- a/src/apis/mentor/patchMenteeCheckMentorings.ts +++ b/src/apis/mentor/patchMenteeCheckMentorings.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { PatchCheckMentoringsRequest, PatchCheckMentoringsResponse, mentorApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { mentorApi, PatchCheckMentoringsRequest, PatchCheckMentoringsResponse } from "./api"; /** * @description 멘티 멘토링 확인 처리 훅 diff --git a/src/apis/mentor/patchMentoringStatus.ts b/src/apis/mentor/patchMentoringStatus.ts index 723f9006..1594dbd7 100644 --- a/src/apis/mentor/patchMentoringStatus.ts +++ b/src/apis/mentor/patchMentoringStatus.ts @@ -1,10 +1,19 @@ import { useRouter } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, PatchApprovalStatusRequest, PatchApprovalStatusResponse, MentoringApprovalStatus } from "./api"; + +import { + MentorQueryKeys, + MentoringApprovalStatus, + PatchApprovalStatusRequest, + PatchApprovalStatusResponse, + mentorApi, +} from "./api"; + import { customAlert } from "@/lib/zustand/useAlertModalStore"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconSmile, IconUnSmile } from "@/public/svgs/mentor"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 멘토링 승인/거절 훅 @@ -52,4 +61,4 @@ const usePatchApprovalStatus = () => { }); }; -export default usePatchApprovalStatus; \ No newline at end of file +export default usePatchApprovalStatus; diff --git a/src/apis/mentor/postApplyMentoring.ts b/src/apis/mentor/postApplyMentoring.ts index bee41d33..7d70022e 100644 --- a/src/apis/mentor/postApplyMentoring.ts +++ b/src/apis/mentor/postApplyMentoring.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, PostApplyMentoringRequest, PostApplyMentoringResponse } from "./api"; + +import { MentorQueryKeys, PostApplyMentoringRequest, PostApplyMentoringResponse, mentorApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 멘토링 신청 훅 @@ -20,4 +22,4 @@ const usePostApplyMentoring = () => { }); }; -export default usePostApplyMentoring; \ No newline at end of file +export default usePostApplyMentoring; diff --git a/src/apis/mentor/postMentorApplication.ts b/src/apis/mentor/postMentorApplication.ts index eee51af0..2ef9acf3 100644 --- a/src/apis/mentor/postMentorApplication.ts +++ b/src/apis/mentor/postMentorApplication.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { mentorApi, PostMentorApplicationRequest } from "./api"; + +import { PostMentorApplicationRequest, mentorApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; /** * @description 멘토 신청 훅 diff --git a/src/apis/mentor/putUpdateMyMentorPage.ts b/src/apis/mentor/putUpdateMyMentorPage.ts index 24fdbb07..23164a8a 100644 --- a/src/apis/mentor/putUpdateMyMentorPage.ts +++ b/src/apis/mentor/putUpdateMyMentorPage.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { MentorQueryKeys, PutMyMentorProfileRequest, mentorApi } from "./api"; + import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { mentorApi, MentorQueryKeys, PutMyMentorProfileRequest } from "./api"; /** * @description 내 멘토 프로필 수정 훅 @@ -19,4 +21,4 @@ const usePutMyMentorProfile = () => { }); }; -export default usePutMyMentorProfile; \ No newline at end of file +export default usePutMyMentorProfile; diff --git a/src/apis/news/api.ts b/src/apis/news/api.ts index 645a4e03..8266572f 100644 --- a/src/apis/news/api.ts +++ b/src/apis/news/api.ts @@ -1,8 +1,11 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; -import { Article } from "@/types/news"; + import { ArticleFormData } from "@/components/mentor/ArticleBottomSheetModal/lib/schema"; +import { Article } from "@/types/news"; + // ====== Query Keys ====== export const NewsQueryKeys = { articleList: "articleList", @@ -103,4 +106,4 @@ export const newsApi = { const response: AxiosResponse = await axiosInstance.delete(`/news/${articleId}/like`); return response.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/news/deleteLikeNews.ts b/src/apis/news/deleteLikeNews.ts index 26041356..c6a58371 100644 --- a/src/apis/news/deleteLikeNews.ts +++ b/src/apis/news/deleteLikeNews.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, DeleteArticleLikeResponse, ArticleListResponse } from "./api"; + +import { ArticleListResponse, DeleteArticleLikeResponse, NewsQueryKeys, newsApi } from "./api"; + import { Article } from "@/types/news"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + type ArticleLikeMutationContext = { previousArticleList?: ArticleListResponse; }; @@ -48,4 +51,4 @@ const useDeleteArticleLike = (userId: number | null) => { }); }; -export default useDeleteArticleLike; \ No newline at end of file +export default useDeleteArticleLike; diff --git a/src/apis/news/deleteNews.ts b/src/apis/news/deleteNews.ts index fdc6fc74..104bf8b6 100644 --- a/src/apis/news/deleteNews.ts +++ b/src/apis/news/deleteNews.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, ArticleListResponse } from "./api"; + +import { ArticleListResponse, NewsQueryKeys, newsApi } from "./api"; + import { Article } from "@/types/news"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; type ArticleDeleteMutationContext = { previousArticleList?: Article[]; @@ -47,4 +50,4 @@ const useDeleteArticle = (userId: number | null) => { }); }; -export default useDeleteArticle; \ No newline at end of file +export default useDeleteArticle; diff --git a/src/apis/news/getNewsList.ts b/src/apis/news/getNewsList.ts index 8bcc4f2a..88a8b149 100644 --- a/src/apis/news/getNewsList.ts +++ b/src/apis/news/getNewsList.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, ArticleListResponse } from "./api"; + +import { ArticleListResponse, NewsQueryKeys, newsApi } from "./api"; + import { Article } from "@/types/news"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 아티클 목록 조회 훅 */ @@ -21,4 +24,4 @@ const useGetArticleList = (userId: number) => { }); }; -export default useGetArticleList; \ No newline at end of file +export default useGetArticleList; diff --git a/src/apis/news/index.ts b/src/apis/news/index.ts index 0e1da051..b4c7f58c 100644 --- a/src/apis/news/index.ts +++ b/src/apis/news/index.ts @@ -1,16 +1,16 @@ -export { newsApi, NewsQueryKeys } from './api'; -export type { - ArticleListResponse, - PostArticleLikeResponse, +export { newsApi, NewsQueryKeys } from "./api"; +export type { + ArticleListResponse, + PostArticleLikeResponse, DeleteArticleLikeResponse, UsePostAddArticleRequest, - UsePutModifyArticleRequest -} from './api'; + UsePutModifyArticleRequest, +} from "./api"; // News (아티클) hooks -export { default as useGetArticleList } from './getNewsList'; -export { default as usePostAddArticle } from './postCreateNews'; -export { default as usePutModifyArticle } from './putUpdateNews'; -export { default as useDeleteArticle } from './deleteNews'; -export { default as usePostArticleLike } from './postLikeNews'; -export { default as useDeleteArticleLike } from './deleteLikeNews'; +export { default as useGetArticleList } from "./getNewsList"; +export { default as usePostAddArticle } from "./postCreateNews"; +export { default as usePutModifyArticle } from "./putUpdateNews"; +export { default as useDeleteArticle } from "./deleteNews"; +export { default as usePostArticleLike } from "./postLikeNews"; +export { default as useDeleteArticleLike } from "./deleteLikeNews"; diff --git a/src/apis/news/postCreateNews.ts b/src/apis/news/postCreateNews.ts index fd5dfe8a..1489c37a 100644 --- a/src/apis/news/postCreateNews.ts +++ b/src/apis/news/postCreateNews.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, UsePostAddArticleRequest, ArticleListResponse } from "./api"; + +import { ArticleListResponse, NewsQueryKeys, UsePostAddArticleRequest, newsApi } from "./api"; + import { Article } from "@/types/news"; + import { toast } from "@/lib/zustand/useToastStore"; import ArticleThumbUrlPng from "@/public/images/article-thumb.png"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; type ArticleMutationContext = { previousArticleContainer?: ArticleListResponse; @@ -54,4 +57,4 @@ const usePostAddArticle = (userId: number | null) => { }); }; -export default usePostAddArticle; \ No newline at end of file +export default usePostAddArticle; diff --git a/src/apis/news/postLikeNews.ts b/src/apis/news/postLikeNews.ts index 606499f1..d71a7e1d 100644 --- a/src/apis/news/postLikeNews.ts +++ b/src/apis/news/postLikeNews.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, PostArticleLikeResponse, ArticleListResponse } from "./api"; + +import { ArticleListResponse, NewsQueryKeys, PostArticleLikeResponse, newsApi } from "./api"; + import { Article } from "@/types/news"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + type ArticleLikeMutationContext = { previousArticleList?: Article[]; }; @@ -48,4 +51,4 @@ const usePostArticleLike = (userId: number | null) => { }); }; -export default usePostArticleLike; \ No newline at end of file +export default usePostArticleLike; diff --git a/src/apis/news/putUpdateNews.ts b/src/apis/news/putUpdateNews.ts index 6c31f9b4..d82ad79a 100644 --- a/src/apis/news/putUpdateNews.ts +++ b/src/apis/news/putUpdateNews.ts @@ -1,8 +1,11 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { newsApi, NewsQueryKeys, UsePutModifyArticleRequest, ArticleListResponse } from "./api"; + +import { ArticleListResponse, NewsQueryKeys, UsePutModifyArticleRequest, newsApi } from "./api"; + import { Article } from "@/types/news"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; type ArticleMutationContext = { previousArticleList?: Article[]; @@ -56,4 +59,4 @@ const usePutModifyArticle = (userId: number | null) => { }); }; -export default usePutModifyArticle; \ No newline at end of file +export default usePutModifyArticle; diff --git a/src/apis/reports/api.ts b/src/apis/reports/api.ts index b31567c8..66f3b2b0 100644 --- a/src/apis/reports/api.ts +++ b/src/apis/reports/api.ts @@ -1,5 +1,7 @@ import { AxiosResponse } from "axios"; + import { axiosInstance } from "@/utils/axiosInstance"; + import { ReportType } from "@/types/reports"; // ====== Types ====== @@ -18,4 +20,4 @@ export const reportsApi = { const response: AxiosResponse = await axiosInstance.post(`/reports`, body); return response.data; }, -}; \ No newline at end of file +}; diff --git a/src/apis/reports/index.ts b/src/apis/reports/index.ts index 684e414a..8baf4cb5 100644 --- a/src/apis/reports/index.ts +++ b/src/apis/reports/index.ts @@ -1,3 +1,3 @@ -export { reportsApi } from './api'; -export type { UsePostReportsRequest } from './api'; -export { default as usePostReports } from './postReport'; +export { reportsApi } from "./api"; +export type { UsePostReportsRequest } from "./api"; +export { default as usePostReports } from "./postReport"; diff --git a/src/apis/reports/postReport.ts b/src/apis/reports/postReport.ts index 10222923..0e4665c0 100644 --- a/src/apis/reports/postReport.ts +++ b/src/apis/reports/postReport.ts @@ -1,8 +1,11 @@ import { useRouter } from "next/navigation"; + import { AxiosError } from "axios"; -import { useMutation } from "@tanstack/react-query"; -import { reportsApi, UsePostReportsRequest } from "./api"; + +import { UsePostReportsRequest, reportsApi } from "./api"; + import { toast } from "@/lib/zustand/useToastStore"; +import { useMutation } from "@tanstack/react-query"; /** * @description 신고 등록 훅 @@ -21,4 +24,4 @@ const usePostReports = () => { }); }; -export default usePostReports; \ No newline at end of file +export default usePostReports; diff --git a/src/apis/universities/api.ts b/src/apis/universities/api.ts index 0dccab72..9ee62b56 100644 --- a/src/apis/universities/api.ts +++ b/src/apis/universities/api.ts @@ -132,66 +132,62 @@ export type ByRegionCountryResponse = void; export const universitiesApi = { getRecommendedUniversities: async (params?: { isLogin?: boolean }): Promise => { const instance = params?.isLogin ? axiosInstance : publicAxiosInstance; - const res = await instance.get( - `/univ-apply-infos/recommend` - ); + const res = await instance.get(`/univ-apply-infos/recommend`); return res.data; }, getWishList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/univ-apply-infos/like`, { params: params?.params } - ); + const res = await axiosInstance.get(`/univ-apply-infos/like`, { params: params?.params }); return res.data; }, deleteWish: async (params: { univApplyInfoId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/univ-apply-infos/${params.univApplyInfoId}/like` - ); + const res = await axiosInstance.delete(`/univ-apply-infos/${params.univApplyInfoId}/like`); return res.data; }, - postAddWish: async (params: { univApplyInfoId: string | number, data?: AddWishRequest }): Promise => { + postAddWish: async (params: { + univApplyInfoId: string | number; + data?: AddWishRequest; + }): Promise => { const res = await axiosInstance.post( - `/univ-apply-infos/${params.univApplyInfoId}/like`, params?.data + `/univ-apply-infos/${params.univApplyInfoId}/like`, + params?.data, ); return res.data; }, - getIsWish: async (params: { univApplyInfoId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/univ-apply-infos/${params.univApplyInfoId}/like`, { params: params?.params } - ); + getIsWish: async (params: { + univApplyInfoId: string | number; + params?: Record; + }): Promise => { + const res = await axiosInstance.get(`/univ-apply-infos/${params.univApplyInfoId}/like`, { + params: params?.params, + }); return res.data; }, getUniversityDetail: async (params: { univApplyInfoId: string | number }): Promise => { - const res = await publicAxiosInstance.get( - `/univ-apply-infos/${params.univApplyInfoId}` - ); + const res = await publicAxiosInstance.get(`/univ-apply-infos/${params.univApplyInfoId}`); return res.data; }, getSearchText: async (params?: { value?: string }): Promise => { - const res = await publicAxiosInstance.get( - `/univ-apply-infos/search/text`, { params: { value: params?.value ?? "" } } - ); + const res = await publicAxiosInstance.get(`/univ-apply-infos/search/text`, { + params: { value: params?.value ?? "" }, + }); return res.data; }, getSearchFilter: async (params?: { params?: Record }): Promise => { - const res = await publicAxiosInstance.get( - `/univ-apply-infos/search/filter`, { params: params?.params } - ); + const res = await publicAxiosInstance.get(`/univ-apply-infos/search/filter`, { + params: params?.params, + }); return res.data; }, getByRegionCountry: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/universities/search`, { params: params?.params } - ); + const res = await axiosInstance.get(`/universities/search`, { params: params?.params }); return res.data; }, - -}; \ No newline at end of file +}; diff --git a/src/apis/universities/deleteWish.ts b/src/apis/universities/deleteWish.ts index f6c015c3..5e3fc309 100644 --- a/src/apis/universities/deleteWish.ts +++ b/src/apis/universities/deleteWish.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { universitiesApi, WishResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { WishResponse, universitiesApi } from "./api"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; /** * @description 위시리스트에서 학교를 삭제하는 useMutation 커스텀 훅 @@ -10,12 +12,11 @@ const useDeleteWish = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (universityInfoForApplyId) => - universitiesApi.deleteWish({ univApplyInfoId: universityInfoForApplyId }), + mutationFn: (universityInfoForApplyId) => universitiesApi.deleteWish({ univApplyInfoId: universityInfoForApplyId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); }, }); }; -export default useDeleteWish; \ No newline at end of file +export default useDeleteWish; diff --git a/src/apis/universities/getRecommendedUniversities.ts b/src/apis/universities/getRecommendedUniversities.ts index 8137bd6c..a316317e 100644 --- a/src/apis/universities/getRecommendedUniversities.ts +++ b/src/apis/universities/getRecommendedUniversities.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, RecommendedUniversitiesResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { RecommendedUniversitiesResponse, universitiesApi } from "./api"; + import { ListUniversity } from "@/types/university"; +import { useQuery } from "@tanstack/react-query"; + type UseGetRecommendedUniversitiesParams = { isLogin: boolean; }; @@ -21,4 +24,4 @@ const useGetRecommendedUniversities = ({ isLogin }: UseGetRecommendedUniversitie }); }; -export default useGetRecommendedUniversities; \ No newline at end of file +export default useGetRecommendedUniversities; diff --git a/src/apis/universities/getSearchFilter.ts b/src/apis/universities/getSearchFilter.ts index 844f9fcc..09b9f3c7 100644 --- a/src/apis/universities/getSearchFilter.ts +++ b/src/apis/universities/getSearchFilter.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, SearchFilterResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { SearchFilterResponse, universitiesApi } from "./api"; + import { CountryCode, LanguageTestType, ListUniversity } from "@/types/university"; +import { useQuery } from "@tanstack/react-query"; + export interface UniversitySearchFilterParams { languageTestType?: LanguageTestType; testScore?: number; @@ -41,4 +44,4 @@ const useGetUniversitySearchByFilter = (filters: UniversitySearchFilterParams) = }); }; -export default useGetUniversitySearchByFilter; \ No newline at end of file +export default useGetUniversitySearchByFilter; diff --git a/src/apis/universities/getSearchText.ts b/src/apis/universities/getSearchText.ts index a322eec1..e0cb52ce 100644 --- a/src/apis/universities/getSearchText.ts +++ b/src/apis/universities/getSearchText.ts @@ -1,10 +1,14 @@ import { useMemo } from "react"; + import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, SearchTextResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { SearchTextResponse, universitiesApi } from "./api"; + import { ListUniversity } from "@/types/university"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 대학 검색을 위한 useQuery 커스텀 훅 * 모든 대학 데이터를 한 번만 가져와 캐싱하고, 검색어에 따라 클라이언트에서 필터링합니다. @@ -37,9 +41,7 @@ const useUniversitySearch = (searchValue: string) => { return []; } - return allUniversities.filter((university) => - university.koreanName.toLowerCase().includes(normalizedSearchValue) - ); + return allUniversities.filter((university) => university.koreanName.toLowerCase().includes(normalizedSearchValue)); }, [allUniversities, searchValue]); return { @@ -51,4 +53,4 @@ const useUniversitySearch = (searchValue: string) => { }; }; -export default useUniversitySearch; \ No newline at end of file +export default useUniversitySearch; diff --git a/src/apis/universities/getUniversityDetail.ts b/src/apis/universities/getUniversityDetail.ts index f6e93c9a..ce2ff998 100644 --- a/src/apis/universities/getUniversityDetail.ts +++ b/src/apis/universities/getUniversityDetail.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, UniversityDetailResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { UniversityDetailResponse, universitiesApi } from "./api"; + import { University } from "@/types/university"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 대학 상세 조회를 위한 useQuery 커스텀 훅 * @param universityInfoForApplyId - 대학 ID @@ -17,4 +20,4 @@ const useGetUniversityDetail = (universityInfoForApplyId: number) => { }); }; -export default useGetUniversityDetail; \ No newline at end of file +export default useGetUniversityDetail; diff --git a/src/apis/universities/getWishList.ts b/src/apis/universities/getWishList.ts index e0b406f1..64fc2953 100644 --- a/src/apis/universities/getWishList.ts +++ b/src/apis/universities/getWishList.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, WishListResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { WishListResponse, universitiesApi } from "./api"; + import { ListUniversity } from "@/types/university"; +import { useQuery } from "@tanstack/react-query"; + /** * @description 내 위시리스트 대학 목록 조회를 위한 useQuery 커스텀 훅 * @param enabled - 쿼리 활성화 여부 @@ -18,4 +21,4 @@ const useGetWishList = (enabled: boolean = true) => { }); }; -export default useGetWishList; \ No newline at end of file +export default useGetWishList; diff --git a/src/apis/universities/index.ts b/src/apis/universities/index.ts index 75eac64d..95a44032 100644 --- a/src/apis/universities/index.ts +++ b/src/apis/universities/index.ts @@ -1,10 +1,10 @@ -export { universitiesApi } from './api'; -export { default as useDeleteWish } from './deleteWish'; -export { default as useGetByRegionCountry } from './getByRegionCountry'; -export { default as useGetIsWish } from './getIsWish'; -export { default as useGetRecommendedUniversities } from './getRecommendedUniversities'; -export { default as useGetUniversitySearchByFilter, type UniversitySearchFilterParams } from './getSearchFilter'; -export { default as useUniversitySearch } from './getSearchText'; -export { default as useGetUniversityDetail } from './getUniversityDetail'; -export { default as useGetWishList } from './getWishList'; -export { default as usePostAddWish } from './postAddWish'; +export { universitiesApi } from "./api"; +export { default as useDeleteWish } from "./deleteWish"; +export { default as useGetByRegionCountry } from "./getByRegionCountry"; +export { default as useGetIsWish } from "./getIsWish"; +export { default as useGetRecommendedUniversities } from "./getRecommendedUniversities"; +export { default as useGetUniversitySearchByFilter, type UniversitySearchFilterParams } from "./getSearchFilter"; +export { default as useUniversitySearch } from "./getSearchText"; +export { default as useGetUniversityDetail } from "./getUniversityDetail"; +export { default as useGetWishList } from "./getWishList"; +export { default as usePostAddWish } from "./postAddWish"; diff --git a/src/apis/universities/postAddWish.ts b/src/apis/universities/postAddWish.ts index 49e5e999..4f7f22bd 100644 --- a/src/apis/universities/postAddWish.ts +++ b/src/apis/universities/postAddWish.ts @@ -1,9 +1,12 @@ import { AxiosError } from "axios"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { universitiesApi, AddWishResponse } from "./api"; -import { QueryKeys } from "../queryKeys"; + import { createMutationErrorHandler } from "@/utils/errorHandler"; +import { QueryKeys } from "../queryKeys"; +import { AddWishResponse, universitiesApi } from "./api"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; + /** * @description 위시리스트에 학교를 추가하는 useMutation 커스텀 훅 */ @@ -20,4 +23,4 @@ const usePostAddWish = () => { }); }; -export default usePostAddWish; \ No newline at end of file +export default usePostAddWish; diff --git a/src/apis/universities/server/index.ts b/src/apis/universities/server/index.ts index 9a1db871..e98228fd 100644 --- a/src/apis/universities/server/index.ts +++ b/src/apis/universities/server/index.ts @@ -1,11 +1,7 @@ // Server-side exports export { default as getRecommendedUniversity } from "./getRecommendedUniversity"; export { getUniversityDetail } from "./getUniversityDetail"; -export { - getUniversitiesByText, - getAllUniversities, - getCategorizedUniversities, -} from "./getSearchUniversitiesByText"; +export { getUniversitiesByText, getAllUniversities, getCategorizedUniversities } from "./getSearchUniversitiesByText"; export { getSearchUniversitiesByFilter, getSearchUniversitiesAllRegions, diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 8295fa87..8b6c4c09 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -7,7 +7,7 @@ import NewsSectionSkeleton from "./_ui/NewsSection/skeleton"; import PopularUniversitySection from "./_ui/PopularUniversitySection"; import UniversityList from "./_ui/UniversityList"; -import { getRecommendedUniversity, getCategorizedUniversities } from "@/apis/universities/server"; +import { getCategorizedUniversities, getRecommendedUniversity } from "@/apis/universities/server"; import { fetchAllNews } from "@/lib/firebaseNews"; import { IconIdCard, IconMagnifyingGlass, IconMuseum, IconPaper } from "@/public/svgs/home"; diff --git a/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx b/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx index c0d5ca7a..d4f2933e 100644 --- a/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx +++ b/src/app/mentor/[id]/_ui/MentorDetialContent/index.tsx @@ -12,7 +12,7 @@ import MentorArticle from "./_ui/MentorArticle"; import { ChannelType } from "@/types/mentor"; -import { usePostApplyMentoring, useGetMentorDetail } from "@/apis/mentor"; +import { useGetMentorDetail, usePostApplyMentoring } from "@/apis/mentor"; import { useGetArticleList } from "@/apis/news"; interface MentorDetailContentProps { diff --git a/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts b/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts index 288c7128..b2131cc5 100644 --- a/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts +++ b/src/app/mentor/modify/_ui/ModifyContent/_hooks/usePutMyMentorProfileHandler.ts @@ -1,6 +1,6 @@ import { MentoModifyFormData } from "../_lib/mentoModifyScehma"; -import { usePutMyMentorProfile, PutMyMentorProfileRequest } from "@/apis/mentor"; +import { PutMyMentorProfileRequest, usePutMyMentorProfile } from "@/apis/mentor"; import { customConfirm } from "@/lib/zustand/useConfirmModalStore"; import { IconModify } from "@/public/svgs/mentor"; diff --git a/src/app/university/SearchResultsContent.tsx b/src/app/university/SearchResultsContent.tsx index 7a0fe9df..9726276d 100644 --- a/src/app/university/SearchResultsContent.tsx +++ b/src/app/university/SearchResultsContent.tsx @@ -14,9 +14,9 @@ import { CountryCode, LanguageTestType, RegionEnumExtend } from "@/types/univers // 필요한 타입과 훅 import import { + type UniversitySearchFilterParams, useGetUniversitySearchByFilter, useUniversitySearch, - type UniversitySearchFilterParams, } from "@/apis/universities"; // --- URL 파라미터를 읽고 데이터를 처리하는 메인 컨텐츠 --- diff --git a/src/app/university/[id]/page.tsx b/src/app/university/[id]/page.tsx index d158d5b6..eae815cc 100644 --- a/src/app/university/[id]/page.tsx +++ b/src/app/university/[id]/page.tsx @@ -5,7 +5,7 @@ import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import UniversityDetail from "./_ui/UniversityDetail"; -import { getUniversityDetail, getAllUniversities } from "@/apis/universities/server"; +import { getAllUniversities, getUniversityDetail } from "@/apis/universities/server"; export const revalidate = false; From fbf24d2b107e50c29f321ad7e368d29aa8e14d7c Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 23:31:35 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix=20:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/login/LoginContent.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/login/LoginContent.tsx b/src/app/login/LoginContent.tsx index c78022b8..4c83d0b9 100644 --- a/src/app/login/LoginContent.tsx +++ b/src/app/login/LoginContent.tsx @@ -84,11 +84,7 @@ const LoginContent = () => { >>>>>> cbc333da (refactor: complete API migration from src/api to src/apis) {...register("email", { onChange: handleEmailChange, })} @@ -105,11 +101,7 @@ const LoginContent = () => { >>>>>> cbc333da (refactor: complete API migration from src/api to src/apis) {...register("password")} onKeyDown={handleKeyDown} /> From 0350187a526c3c9b1359d2fd0f749804dbf12a48 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 23:33:13 +0900 Subject: [PATCH 07/11] =?UTF-8?q?fix=20:=20=EB=A6=B0=ED=8A=B8=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../university/UniversityCards/index.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/university/UniversityCards/index.tsx b/src/components/university/UniversityCards/index.tsx index c2bae983..efbc41ca 100644 --- a/src/components/university/UniversityCards/index.tsx +++ b/src/components/university/UniversityCards/index.tsx @@ -27,6 +27,16 @@ const UniversityCards = ({ showCapacity = true, enableVirtualization = true, }: UniversityCardsProps) => { + // 훅은 항상 컴포넌트 상단에서 호출해야 함 (React Hooks 규칙) + const parentRef = useRef(null); + + const virtualizer = useVirtualizer({ + count: colleges.length, + getScrollElement: () => parentRef.current, + estimateSize: () => ITEM_HEIGHT, + overscan: 5, + }); + // 가상화가 비활성화된 경우 일반 렌더링 if (!enableVirtualization) { return ( @@ -40,16 +50,7 @@ const UniversityCards = ({ ); } - // 가상화 사용 (기존 로직) - const parentRef = useRef(null); - - const virtualizer = useVirtualizer({ - count: colleges.length, - getScrollElement: () => parentRef.current, - estimateSize: () => ITEM_HEIGHT, - overscan: 5, - }); - + // 가상화 사용 return (
Date: Sun, 28 Dec 2025 23:36:10 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix=20:=20=EB=A6=B0=ED=8A=B8=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/community/[boardCode]/page.tsx | 3 ++- src/utils/axiosInstance.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/community/[boardCode]/page.tsx b/src/app/community/[boardCode]/page.tsx index ea02d013..ebd7d7b3 100644 --- a/src/app/community/[boardCode]/page.tsx +++ b/src/app/community/[boardCode]/page.tsx @@ -6,7 +6,8 @@ import CommunityPageContent from "./CommunityPageContent"; import { COMMUNITY_BOARDS } from "@/constants/community"; -import { CommunityQueryKeys, getPostListServer } from "@/apis/community"; +import { CommunityQueryKeys } from "@/apis/community/api"; +import { getPostListServer } from "@/apis/community/server"; import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query"; export const metadata: Metadata = { diff --git a/src/utils/axiosInstance.ts b/src/utils/axiosInstance.ts index 7080b763..24a12011 100644 --- a/src/utils/axiosInstance.ts +++ b/src/utils/axiosInstance.ts @@ -1,6 +1,6 @@ import axios, { AxiosError, AxiosInstance } from "axios"; -import { postReissueToken } from "@/apis/Auth"; +import { postReissueToken } from "@/apis/Auth/server"; import useAuthStore from "@/lib/zustand/useAuthStore"; import { toast } from "@/lib/zustand/useToastStore"; From cee46586e56a41c1e4e8383d536e83891acab80f Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 23:38:20 +0900 Subject: [PATCH 09/11] =?UTF-8?q?fix=20:=20=EB=A6=B0=ED=8A=B8=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/Admin/api.ts | 30 +-- src/apis/Admin/getGpaList.ts | 8 +- src/apis/Admin/getLanguageTestList.ts | 8 +- src/apis/Admin/index.ts | 10 +- src/apis/Admin/putVerifyGpa.ts | 6 +- src/apis/Admin/putVerifyLanguageTest.ts | 12 +- src/apis/Auth/postRefreshToken.ts | 6 +- src/apis/applications/getCompetitors.ts | 8 +- src/apis/community/getBoard.ts | 8 +- src/apis/community/getBoardList.ts | 8 +- src/apis/community/patchUpdateComment.ts | 6 +- .../image-upload/postSlackNotification.ts | 6 +- src/apis/image-upload/postUploadGpaReport.ts | 6 +- .../postUploadLanguageTestReport.ts | 6 +- .../image-upload/postUploadProfileImage.ts | 6 +- src/apis/kakao-api/api.ts | 15 +- src/apis/kakao-api/getKakaoInfo.ts | 10 +- src/apis/kakao-api/getKakaoUserIds.ts | 10 +- src/apis/kakao-api/index.ts | 8 +- src/apis/kakao-api/postKakaoUnlink.ts | 6 +- src/apis/mentor/getMatchedMentors.ts | 14 +- .../mentor/legacy/patchConfirmMentoring.ts | 6 +- src/apis/queryKeys.ts | 194 +++++++++--------- src/apis/universities/getByRegionCountry.ts | 8 +- src/apis/universities/getIsWish.ts | 8 +- src/apis/users/api.ts | 26 +-- src/apis/users/deleteUnblockUser.ts | 6 +- src/apis/users/getBlockedUsers.ts | 8 +- src/apis/users/getNicknameExists.ts | 8 +- src/apis/users/index.ts | 10 +- src/apis/users/postBlockUser.ts | 6 +- 31 files changed, 266 insertions(+), 211 deletions(-) diff --git a/src/apis/Admin/api.ts b/src/apis/Admin/api.ts index 48752a1d..74b67368 100644 --- a/src/apis/Admin/api.ts +++ b/src/apis/Admin/api.ts @@ -143,32 +143,34 @@ export interface GpaListResponse { } export const adminApi = { - putVerifyLanguageTest: async (params: { languageTestScoreId: string | number, data?: VerifyLanguageTestRequest }): Promise => { + putVerifyLanguageTest: async (params: { + languageTestScoreId: string | number; + data?: VerifyLanguageTestRequest; + }): Promise => { const res = await axiosInstance.put( - `/admin/scores/language-tests/${params.languageTestScoreId}`, params?.data + `/admin/scores/language-tests/${params.languageTestScoreId}`, + params?.data, ); return res.data; }, getLanguageTestList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/admin/scores/language-tests?page=1&size=10`, { params: params?.params } - ); + const res = await axiosInstance.get(`/admin/scores/language-tests?page=1&size=10`, { + params: params?.params, + }); return res.data; }, - putVerifyGpa: async (params: { gpaScoreId: string | number, data?: VerifyGpaRequest }): Promise => { - const res = await axiosInstance.put( - `/admin/scores/gpas/${params.gpaScoreId}`, params?.data - ); + putVerifyGpa: async (params: { + gpaScoreId: string | number; + data?: VerifyGpaRequest; + }): Promise => { + const res = await axiosInstance.put(`/admin/scores/gpas/${params.gpaScoreId}`, params?.data); return res.data; }, getGpaList: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/admin/scores/gpas`, { params: params?.params } - ); + const res = await axiosInstance.get(`/admin/scores/gpas`, { params: params?.params }); return res.data; }, - -}; \ No newline at end of file +}; diff --git a/src/apis/Admin/getGpaList.ts b/src/apis/Admin/getGpaList.ts index d3ae89e6..cacb3db3 100644 --- a/src/apis/Admin/getGpaList.ts +++ b/src/apis/Admin/getGpaList.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { adminApi, GpaListResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { GpaListResponse, adminApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetGpaList = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetGpaList = (params?: Record) => { }); }; -export default useGetGpaList; \ No newline at end of file +export default useGetGpaList; diff --git a/src/apis/Admin/getLanguageTestList.ts b/src/apis/Admin/getLanguageTestList.ts index 6d9325d8..e30603a4 100644 --- a/src/apis/Admin/getLanguageTestList.ts +++ b/src/apis/Admin/getLanguageTestList.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { adminApi, LanguageTestListResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { LanguageTestListResponse, adminApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetLanguageTestList = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetLanguageTestList = (params?: Record) => { }); }; -export default useGetLanguageTestList; \ No newline at end of file +export default useGetLanguageTestList; diff --git a/src/apis/Admin/index.ts b/src/apis/Admin/index.ts index d5264145..f7d22c2a 100644 --- a/src/apis/Admin/index.ts +++ b/src/apis/Admin/index.ts @@ -1,5 +1,5 @@ -export { adminApi } from './api'; -export { default as getGpaList } from './getGpaList'; -export { default as getLanguageTestList } from './getLanguageTestList'; -export { default as putVerifyGpa } from './putVerifyGpa'; -export { default as putVerifyLanguageTest } from './putVerifyLanguageTest'; +export { adminApi } from "./api"; +export { default as getGpaList } from "./getGpaList"; +export { default as getLanguageTestList } from "./getLanguageTestList"; +export { default as putVerifyGpa } from "./putVerifyGpa"; +export { default as putVerifyLanguageTest } from "./putVerifyLanguageTest"; diff --git a/src/apis/Admin/putVerifyGpa.ts b/src/apis/Admin/putVerifyGpa.ts index 8073d81a..4bd564f4 100644 --- a/src/apis/Admin/putVerifyGpa.ts +++ b/src/apis/Admin/putVerifyGpa.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { VerifyGpaRequest, VerifyGpaResponse, adminApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { adminApi, VerifyGpaResponse, VerifyGpaRequest } from "./api"; const usePutVerifyGpa = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePutVerifyGpa = () => { }); }; -export default usePutVerifyGpa; \ No newline at end of file +export default usePutVerifyGpa; diff --git a/src/apis/Admin/putVerifyLanguageTest.ts b/src/apis/Admin/putVerifyLanguageTest.ts index fa37c5e4..f83b3e6b 100644 --- a/src/apis/Admin/putVerifyLanguageTest.ts +++ b/src/apis/Admin/putVerifyLanguageTest.ts @@ -1,11 +1,17 @@ import { AxiosError } from "axios"; + +import { VerifyLanguageTestRequest, VerifyLanguageTestResponse, adminApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { adminApi, VerifyLanguageTestResponse, VerifyLanguageTestRequest } from "./api"; const usePutVerifyLanguageTest = () => { - return useMutation({ + return useMutation< + VerifyLanguageTestResponse, + AxiosError, + { languageTestScoreId: string | number; data: VerifyLanguageTestRequest } + >({ mutationFn: (variables) => adminApi.putVerifyLanguageTest(variables), }); }; -export default usePutVerifyLanguageTest; \ No newline at end of file +export default usePutVerifyLanguageTest; diff --git a/src/apis/Auth/postRefreshToken.ts b/src/apis/Auth/postRefreshToken.ts index ae86a780..ba38a805 100644 --- a/src/apis/Auth/postRefreshToken.ts +++ b/src/apis/Auth/postRefreshToken.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { RefreshTokenRequest, RefreshTokenResponse, authApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { authApi, RefreshTokenResponse, RefreshTokenRequest } from "./api"; const usePostRefreshToken = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostRefreshToken = () => { }); }; -export default usePostRefreshToken; \ No newline at end of file +export default usePostRefreshToken; diff --git a/src/apis/applications/getCompetitors.ts b/src/apis/applications/getCompetitors.ts index 4d3ccbc9..c0696094 100644 --- a/src/apis/applications/getCompetitors.ts +++ b/src/apis/applications/getCompetitors.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { applicationsApi, CompetitorsResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { CompetitorsResponse, applicationsApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetCompetitors = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetCompetitors = (params?: Record) => { }); }; -export default useGetCompetitors; \ No newline at end of file +export default useGetCompetitors; diff --git a/src/apis/community/getBoard.ts b/src/apis/community/getBoard.ts index 1ea2869d..b5922eaf 100644 --- a/src/apis/community/getBoard.ts +++ b/src/apis/community/getBoard.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { communityApi, BoardResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { BoardResponse, communityApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetBoard = (boardCode: string | number, params?: Record) => { return useQuery({ @@ -11,4 +13,4 @@ const useGetBoard = (boardCode: string | number, params?: Record) = }); }; -export default useGetBoard; \ No newline at end of file +export default useGetBoard; diff --git a/src/apis/community/getBoardList.ts b/src/apis/community/getBoardList.ts index bcb0d65b..80bbbe69 100644 --- a/src/apis/community/getBoardList.ts +++ b/src/apis/community/getBoardList.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { communityApi, BoardListResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { BoardListResponse, communityApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetBoardList = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetBoardList = (params?: Record) => { }); }; -export default useGetBoardList; \ No newline at end of file +export default useGetBoardList; diff --git a/src/apis/community/patchUpdateComment.ts b/src/apis/community/patchUpdateComment.ts index 3a0fbfe6..1ebe8d06 100644 --- a/src/apis/community/patchUpdateComment.ts +++ b/src/apis/community/patchUpdateComment.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { UpdateCommentRequest, UpdateCommentResponse, communityApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { communityApi, UpdateCommentResponse, UpdateCommentRequest } from "./api"; const usePatchUpdateComment = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePatchUpdateComment = () => { }); }; -export default usePatchUpdateComment; \ No newline at end of file +export default usePatchUpdateComment; diff --git a/src/apis/image-upload/postSlackNotification.ts b/src/apis/image-upload/postSlackNotification.ts index c973ec12..c5ea6962 100644 --- a/src/apis/image-upload/postSlackNotification.ts +++ b/src/apis/image-upload/postSlackNotification.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { SlackNotificationRequest, SlackNotificationResponse, imageUploadApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { imageUploadApi, SlackNotificationResponse, SlackNotificationRequest } from "./api"; const usePostSlackNotification = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostSlackNotification = () => { }); }; -export default usePostSlackNotification; \ No newline at end of file +export default usePostSlackNotification; diff --git a/src/apis/image-upload/postUploadGpaReport.ts b/src/apis/image-upload/postUploadGpaReport.ts index 06ed6ece..aeab6da2 100644 --- a/src/apis/image-upload/postUploadGpaReport.ts +++ b/src/apis/image-upload/postUploadGpaReport.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { UploadGpaReportRequest, UploadGpaReportResponse, imageUploadApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { imageUploadApi, UploadGpaReportResponse, UploadGpaReportRequest } from "./api"; const usePostUploadGpaReport = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostUploadGpaReport = () => { }); }; -export default usePostUploadGpaReport; \ No newline at end of file +export default usePostUploadGpaReport; diff --git a/src/apis/image-upload/postUploadLanguageTestReport.ts b/src/apis/image-upload/postUploadLanguageTestReport.ts index 7b36f675..47837e87 100644 --- a/src/apis/image-upload/postUploadLanguageTestReport.ts +++ b/src/apis/image-upload/postUploadLanguageTestReport.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { UploadLanguageTestReportRequest, UploadLanguageTestReportResponse, imageUploadApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { imageUploadApi, UploadLanguageTestReportResponse, UploadLanguageTestReportRequest } from "./api"; const usePostUploadLanguageTestReport = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostUploadLanguageTestReport = () => { }); }; -export default usePostUploadLanguageTestReport; \ No newline at end of file +export default usePostUploadLanguageTestReport; diff --git a/src/apis/image-upload/postUploadProfileImage.ts b/src/apis/image-upload/postUploadProfileImage.ts index 72db4ae2..fe738c9f 100644 --- a/src/apis/image-upload/postUploadProfileImage.ts +++ b/src/apis/image-upload/postUploadProfileImage.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { UploadProfileImageRequest, UploadProfileImageResponse, imageUploadApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { imageUploadApi, UploadProfileImageResponse, UploadProfileImageRequest } from "./api"; const usePostUploadProfileImage = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostUploadProfileImage = () => { }); }; -export default usePostUploadProfileImage; \ No newline at end of file +export default usePostUploadProfileImage; diff --git a/src/apis/kakao-api/api.ts b/src/apis/kakao-api/api.ts index 51eda46d..54b9cfac 100644 --- a/src/apis/kakao-api/api.ts +++ b/src/apis/kakao-api/api.ts @@ -10,24 +10,25 @@ export type KakaoInfoResponse = void; export const kakaoApiApi = { getKakaoUserIds: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `https://kapi.kakao.com/v1/user/ids?order=dsc`, { params: params?.params } - ); + const res = await axiosInstance.get(`https://kapi.kakao.com/v1/user/ids?order=dsc`, { + params: params?.params, + }); return res.data; }, postKakaoUnlink: async (params: { data?: KakaoUnlinkRequest }): Promise => { const res = await axiosInstance.post( - `https://kapi.kakao.com/v1/user/unlink?target_id_type=user_id&target_id=3715136239`, params?.data + `https://kapi.kakao.com/v1/user/unlink?target_id_type=user_id&target_id=3715136239`, + params?.data, ); return res.data; }, getKakaoInfo: async (params: { params?: Record }): Promise => { const res = await axiosInstance.get( - `https://kapi.kakao.com/v2/user/me?property_keys=["kakao_account.email"]&target_id_type=user_id&target_id=3715136239`, { params: params?.params } + `https://kapi.kakao.com/v2/user/me?property_keys=["kakao_account.email"]&target_id_type=user_id&target_id=3715136239`, + { params: params?.params }, ); return res.data; }, - -}; \ No newline at end of file +}; diff --git a/src/apis/kakao-api/getKakaoInfo.ts b/src/apis/kakao-api/getKakaoInfo.ts index 92c82449..53a4da37 100644 --- a/src/apis/kakao-api/getKakaoInfo.ts +++ b/src/apis/kakao-api/getKakaoInfo.ts @@ -1,13 +1,15 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { kakaoApiApi, KakaoInfoResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { KakaoInfoResponse, kakaoApiApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetKakaoInfo = (params?: Record) => { return useQuery({ - queryKey: [QueryKeys['kakao-api'].kakaoInfo, params], + queryKey: [QueryKeys["kakao-api"].kakaoInfo, params], queryFn: () => kakaoApiApi.getKakaoInfo(params ? { params } : {}), }); }; -export default useGetKakaoInfo; \ No newline at end of file +export default useGetKakaoInfo; diff --git a/src/apis/kakao-api/getKakaoUserIds.ts b/src/apis/kakao-api/getKakaoUserIds.ts index dc85cac5..a6c1e6bd 100644 --- a/src/apis/kakao-api/getKakaoUserIds.ts +++ b/src/apis/kakao-api/getKakaoUserIds.ts @@ -1,13 +1,15 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { kakaoApiApi, KakaoUserIdsResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { KakaoUserIdsResponse, kakaoApiApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetKakaoUserIds = (params?: Record) => { return useQuery({ - queryKey: [QueryKeys['kakao-api'].kakaoUserIds, params], + queryKey: [QueryKeys["kakao-api"].kakaoUserIds, params], queryFn: () => kakaoApiApi.getKakaoUserIds(params ? { params } : {}), }); }; -export default useGetKakaoUserIds; \ No newline at end of file +export default useGetKakaoUserIds; diff --git a/src/apis/kakao-api/index.ts b/src/apis/kakao-api/index.ts index bd6c3ae8..0acb2db7 100644 --- a/src/apis/kakao-api/index.ts +++ b/src/apis/kakao-api/index.ts @@ -1,4 +1,4 @@ -export { kakaoApiApi } from './api'; -export { default as getKakaoInfo } from './getKakaoInfo'; -export { default as getKakaoUserIds } from './getKakaoUserIds'; -export { default as postKakaoUnlink } from './postKakaoUnlink'; +export { kakaoApiApi } from "./api"; +export { default as getKakaoInfo } from "./getKakaoInfo"; +export { default as getKakaoUserIds } from "./getKakaoUserIds"; +export { default as postKakaoUnlink } from "./postKakaoUnlink"; diff --git a/src/apis/kakao-api/postKakaoUnlink.ts b/src/apis/kakao-api/postKakaoUnlink.ts index c20d19d0..268eae6c 100644 --- a/src/apis/kakao-api/postKakaoUnlink.ts +++ b/src/apis/kakao-api/postKakaoUnlink.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { KakaoUnlinkRequest, KakaoUnlinkResponse, kakaoApiApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { kakaoApiApi, KakaoUnlinkResponse, KakaoUnlinkRequest } from "./api"; const usePostKakaoUnlink = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostKakaoUnlink = () => { }); }; -export default usePostKakaoUnlink; \ No newline at end of file +export default usePostKakaoUnlink; diff --git a/src/apis/mentor/getMatchedMentors.ts b/src/apis/mentor/getMatchedMentors.ts index 40ff31b9..46402b2d 100644 --- a/src/apis/mentor/getMatchedMentors.ts +++ b/src/apis/mentor/getMatchedMentors.ts @@ -1,9 +1,15 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { mentorApi, MatchedMentorsResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { MatchedMentorsResponse, mentorApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; -const useGetMatchedMentors = (defaultSize: string | number, defaultPage: string | number, params?: Record) => { +const useGetMatchedMentors = ( + defaultSize: string | number, + defaultPage: string | number, + params?: Record, +) => { return useQuery({ queryKey: [QueryKeys.mentor.matchedMentors, defaultSize, defaultPage, params], queryFn: () => mentorApi.getMatchedMentors({ defaultSize, defaultPage, params }), @@ -11,4 +17,4 @@ const useGetMatchedMentors = (defaultSize: string | number, defaultPage: string }); }; -export default useGetMatchedMentors; \ No newline at end of file +export default useGetMatchedMentors; diff --git a/src/apis/mentor/legacy/patchConfirmMentoring.ts b/src/apis/mentor/legacy/patchConfirmMentoring.ts index 43e07f3f..deb30c75 100644 --- a/src/apis/mentor/legacy/patchConfirmMentoring.ts +++ b/src/apis/mentor/legacy/patchConfirmMentoring.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { ConfirmMentoringRequest, ConfirmMentoringResponse, mentorApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { mentorApi, ConfirmMentoringResponse, ConfirmMentoringRequest } from "./api"; const usePatchConfirmMentoring = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePatchConfirmMentoring = () => { }); }; -export default usePatchConfirmMentoring; \ No newline at end of file +export default usePatchConfirmMentoring; diff --git a/src/apis/queryKeys.ts b/src/apis/queryKeys.ts index 2a1d2c8b..3963ad5a 100644 --- a/src/apis/queryKeys.ts +++ b/src/apis/queryKeys.ts @@ -5,128 +5,128 @@ export const QueryKeys = { Auth: { - folder: 'Auth.folder' as const, - signOut: 'Auth.signOut' as const, - appleAuth: 'Auth.appleAuth' as const, - refreshToken: 'Auth.refreshToken' as const, - emailLogin: 'Auth.emailLogin' as const, - emailVerification: 'Auth.emailVerification' as const, - kakaoAuth: 'Auth.kakaoAuth' as const, - account: 'Auth.account' as const, - signUp: 'Auth.signUp' as const, + folder: "Auth.folder" as const, + signOut: "Auth.signOut" as const, + appleAuth: "Auth.appleAuth" as const, + refreshToken: "Auth.refreshToken" as const, + emailLogin: "Auth.emailLogin" as const, + emailVerification: "Auth.emailVerification" as const, + kakaoAuth: "Auth.kakaoAuth" as const, + account: "Auth.account" as const, + signUp: "Auth.signUp" as const, }, news: { - folder: 'news.folder' as const, - newsList: 'news.newsList' as const, - news: 'news.news' as const, - updateNews: 'news.updateNews' as const, - likeNews: 'news.likeNews' as const, - createNews: 'news.createNews' as const, + folder: "news.folder" as const, + newsList: "news.newsList" as const, + news: "news.news" as const, + updateNews: "news.updateNews" as const, + likeNews: "news.likeNews" as const, + createNews: "news.createNews" as const, }, reports: { - folder: 'reports.folder' as const, - report: 'reports.report' as const, + folder: "reports.folder" as const, + report: "reports.report" as const, }, chat: { - folder: 'chat.folder' as const, - chatMessages: 'chat.chatMessages' as const, - chatRooms: 'chat.chatRooms' as const, - readChatRoom: 'chat.readChatRoom' as const, - chatPartner: 'chat.chatPartner' as const, + folder: "chat.folder" as const, + chatMessages: "chat.chatMessages" as const, + chatRooms: "chat.chatRooms" as const, + readChatRoom: "chat.readChatRoom" as const, + chatPartner: "chat.chatPartner" as const, }, universities: { - folder: 'universities.folder' as const, - recommendedUniversities: 'universities.recommendedUniversities' as const, - wishList: 'universities.wishList' as const, - wish: 'universities.wish' as const, - addWish: 'universities.addWish' as const, - isWish: 'universities.isWish' as const, - universityDetail: 'universities.universityDetail' as const, - searchText: 'universities.searchText' as const, - searchFilter: 'universities.searchFilter' as const, - byRegionCountry: 'universities.byRegionCountry' as const, + folder: "universities.folder" as const, + recommendedUniversities: "universities.recommendedUniversities" as const, + wishList: "universities.wishList" as const, + wish: "universities.wish" as const, + addWish: "universities.addWish" as const, + isWish: "universities.isWish" as const, + universityDetail: "universities.universityDetail" as const, + searchText: "universities.searchText" as const, + searchFilter: "universities.searchFilter" as const, + byRegionCountry: "universities.byRegionCountry" as const, }, MyPage: { - folder: 'MyPage.folder' as const, - interestedRegionCountry: 'MyPage.interestedRegionCountry' as const, - profile: 'MyPage.profile' as const, - password: 'MyPage.password' as const, + folder: "MyPage.folder" as const, + interestedRegionCountry: "MyPage.interestedRegionCountry" as const, + profile: "MyPage.profile" as const, + password: "MyPage.password" as const, }, applications: { - folder: 'applications.folder' as const, - competitors: 'applications.competitors' as const, - submitApplication: 'applications.submitApplication' as const, - applicants: 'applications.applicants' as const, + folder: "applications.folder" as const, + competitors: "applications.competitors" as const, + submitApplication: "applications.submitApplication" as const, + applicants: "applications.applicants" as const, }, community: { - folder: 'community.folder' as const, - boardList: 'community.boardList' as const, - board: 'community.board' as const, - comment: 'community.comment' as const, - updateComment: 'community.updateComment' as const, - createComment: 'community.createComment' as const, - post: 'community.post' as const, - updatePost: 'community.updatePost' as const, - createPost: 'community.createPost' as const, - postDetail: 'community.postDetail' as const, - likePost: 'community.likePost' as const, + folder: "community.folder" as const, + boardList: "community.boardList" as const, + board: "community.board" as const, + comment: "community.comment" as const, + updateComment: "community.updateComment" as const, + createComment: "community.createComment" as const, + post: "community.post" as const, + updatePost: "community.updatePost" as const, + createPost: "community.createPost" as const, + postDetail: "community.postDetail" as const, + likePost: "community.likePost" as const, }, Scores: { - folder: 'Scores.folder' as const, - createLanguageTest: 'Scores.createLanguageTest' as const, - languageTestList: 'Scores.languageTestList' as const, - createGpa: 'Scores.createGpa' as const, - gpaList: 'Scores.gpaList' as const, + folder: "Scores.folder" as const, + createLanguageTest: "Scores.createLanguageTest" as const, + languageTestList: "Scores.languageTestList" as const, + createGpa: "Scores.createGpa" as const, + gpaList: "Scores.gpaList" as const, }, Admin: { - folder: 'Admin.folder' as const, - verifyLanguageTest: 'Admin.verifyLanguageTest' as const, - languageTestList: 'Admin.languageTestList' as const, - verifyGpa: 'Admin.verifyGpa' as const, - gpaList: 'Admin.gpaList' as const, + folder: "Admin.folder" as const, + verifyLanguageTest: "Admin.verifyLanguageTest" as const, + languageTestList: "Admin.languageTestList" as const, + verifyGpa: "Admin.verifyGpa" as const, + gpaList: "Admin.gpaList" as const, }, users: { - folder: 'users.folder' as const, - nicknameExists: 'users.nicknameExists' as const, - blockUser: 'users.blockUser' as const, - unblockUser: 'users.unblockUser' as const, - blockedUsers: 'users.blockedUsers' as const, + folder: "users.folder" as const, + nicknameExists: "users.nicknameExists" as const, + blockUser: "users.blockUser" as const, + unblockUser: "users.unblockUser" as const, + blockedUsers: "users.blockedUsers" as const, }, mentor: { - folder: 'mentor.folder' as const, - matchedMentors: 'mentor.matchedMentors' as const, - applyMentoring: 'mentor.applyMentoring' as const, - confirmMentoring: 'mentor.confirmMentoring' as const, - appliedMentorings: 'mentor.appliedMentorings' as const, - mentorList: 'mentor.mentorList' as const, - mentorDetail: 'mentor.mentorDetail' as const, - myMentorPage: 'mentor.myMentorPage' as const, - updateMyMentorPage: 'mentor.updateMyMentorPage' as const, - mentoringStatus: 'mentor.mentoringStatus' as const, - receivedMentorings: 'mentor.receivedMentorings' as const, - unconfirmedMentoringCount: 'mentor.unconfirmedMentoringCount' as const, - }, - 'kakao-api': { - folder: 'kakao-api.folder' as const, - kakaoUserIds: 'kakao-api.kakaoUserIds' as const, - kakaoUnlink: 'kakao-api.kakaoUnlink' as const, - kakaoInfo: 'kakao-api.kakaoInfo' as const, - }, - 'collection.bru': { - collection: 'collection.bru.collection' as const, + folder: "mentor.folder" as const, + matchedMentors: "mentor.matchedMentors" as const, + applyMentoring: "mentor.applyMentoring" as const, + confirmMentoring: "mentor.confirmMentoring" as const, + appliedMentorings: "mentor.appliedMentorings" as const, + mentorList: "mentor.mentorList" as const, + mentorDetail: "mentor.mentorDetail" as const, + myMentorPage: "mentor.myMentorPage" as const, + updateMyMentorPage: "mentor.updateMyMentorPage" as const, + mentoringStatus: "mentor.mentoringStatus" as const, + receivedMentorings: "mentor.receivedMentorings" as const, + unconfirmedMentoringCount: "mentor.unconfirmedMentoringCount" as const, + }, + "kakao-api": { + folder: "kakao-api.folder" as const, + kakaoUserIds: "kakao-api.kakaoUserIds" as const, + kakaoUnlink: "kakao-api.kakaoUnlink" as const, + kakaoInfo: "kakao-api.kakaoInfo" as const, + }, + "collection.bru": { + collection: "collection.bru.collection" as const, }, environments: { - dev: 'environments.dev' as const, - local: 'environments.local' as const, + dev: "environments.dev" as const, + local: "environments.local" as const, }, - 'image-upload': { - folder: 'image-upload.folder' as const, - slackNotification: 'image-upload.slackNotification' as const, - uploadLanguageTestReport: 'image-upload.uploadLanguageTestReport' as const, - uploadProfileImage: 'image-upload.uploadProfileImage' as const, - uploadProfileImageBeforeSignup: 'image-upload.uploadProfileImageBeforeSignup' as const, - uploadGpaReport: 'image-upload.uploadGpaReport' as const, + "image-upload": { + folder: "image-upload.folder" as const, + slackNotification: "image-upload.slackNotification" as const, + uploadLanguageTestReport: "image-upload.uploadLanguageTestReport" as const, + uploadProfileImage: "image-upload.uploadProfileImage" as const, + uploadProfileImageBeforeSignup: "image-upload.uploadProfileImageBeforeSignup" as const, + uploadGpaReport: "image-upload.uploadGpaReport" as const, }, } as const; -export type QueryKey = typeof QueryKeys[keyof typeof QueryKeys]; \ No newline at end of file +export type QueryKey = (typeof QueryKeys)[keyof typeof QueryKeys]; diff --git a/src/apis/universities/getByRegionCountry.ts b/src/apis/universities/getByRegionCountry.ts index baa84596..29f34b56 100644 --- a/src/apis/universities/getByRegionCountry.ts +++ b/src/apis/universities/getByRegionCountry.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, ByRegionCountryResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { ByRegionCountryResponse, universitiesApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetByRegionCountry = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetByRegionCountry = (params?: Record) => { }); }; -export default useGetByRegionCountry; \ No newline at end of file +export default useGetByRegionCountry; diff --git a/src/apis/universities/getIsWish.ts b/src/apis/universities/getIsWish.ts index 567a0887..f89e3f25 100644 --- a/src/apis/universities/getIsWish.ts +++ b/src/apis/universities/getIsWish.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { universitiesApi, IsWishResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { IsWishResponse, universitiesApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetIsWish = (univApplyInfoId: string | number, params?: Record) => { return useQuery({ @@ -11,4 +13,4 @@ const useGetIsWish = (univApplyInfoId: string | number, params?: Record }): Promise => { - const res = await axiosInstance.get( - `/users/exists?nickname=abc`, { params: params?.params } - ); + const res = await axiosInstance.get(`/users/exists?nickname=abc`, { + params: params?.params, + }); return res.data; }, - postBlockUser: async (params: { blockedId: string | number, data?: BlockUserRequest }): Promise => { - const res = await axiosInstance.post( - `/users/block/${params.blockedId}`, params?.data - ); + postBlockUser: async (params: { + blockedId: string | number; + data?: BlockUserRequest; + }): Promise => { + const res = await axiosInstance.post(`/users/block/${params.blockedId}`, params?.data); return res.data; }, deleteUnblockUser: async (params: { blockedId: string | number }): Promise => { - const res = await axiosInstance.delete( - `/users/block/${params.blockedId}` - ); + const res = await axiosInstance.delete(`/users/block/${params.blockedId}`); return res.data; }, getBlockedUsers: async (params: { params?: Record }): Promise => { - const res = await axiosInstance.get( - `/users/blocks`, { params: params?.params } - ); + const res = await axiosInstance.get(`/users/blocks`, { params: params?.params }); return res.data; }, - -}; \ No newline at end of file +}; diff --git a/src/apis/users/deleteUnblockUser.ts b/src/apis/users/deleteUnblockUser.ts index 385dd097..88cb8451 100644 --- a/src/apis/users/deleteUnblockUser.ts +++ b/src/apis/users/deleteUnblockUser.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { UnblockUserRequest, UnblockUserResponse, usersApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { usersApi, UnblockUserResponse, UnblockUserRequest } from "./api"; const useDeleteUnblockUser = () => { return useMutation({ @@ -8,4 +10,4 @@ const useDeleteUnblockUser = () => { }); }; -export default useDeleteUnblockUser; \ No newline at end of file +export default useDeleteUnblockUser; diff --git a/src/apis/users/getBlockedUsers.ts b/src/apis/users/getBlockedUsers.ts index 1681e5dd..4e33e910 100644 --- a/src/apis/users/getBlockedUsers.ts +++ b/src/apis/users/getBlockedUsers.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { usersApi, BlockedUsersResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { BlockedUsersResponse, usersApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetBlockedUsers = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetBlockedUsers = (params?: Record) => { }); }; -export default useGetBlockedUsers; \ No newline at end of file +export default useGetBlockedUsers; diff --git a/src/apis/users/getNicknameExists.ts b/src/apis/users/getNicknameExists.ts index 7793059c..fc5ff88a 100644 --- a/src/apis/users/getNicknameExists.ts +++ b/src/apis/users/getNicknameExists.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; -import { useQuery } from "@tanstack/react-query"; -import { usersApi, NicknameExistsResponse } from "./api"; + import { QueryKeys } from "../queryKeys"; +import { NicknameExistsResponse, usersApi } from "./api"; + +import { useQuery } from "@tanstack/react-query"; const useGetNicknameExists = (params?: Record) => { return useQuery({ @@ -10,4 +12,4 @@ const useGetNicknameExists = (params?: Record) => { }); }; -export default useGetNicknameExists; \ No newline at end of file +export default useGetNicknameExists; diff --git a/src/apis/users/index.ts b/src/apis/users/index.ts index 6b491bd6..ff51c891 100644 --- a/src/apis/users/index.ts +++ b/src/apis/users/index.ts @@ -1,5 +1,5 @@ -export { usersApi } from './api'; -export { default as deleteUnblockUser } from './deleteUnblockUser'; -export { default as getBlockedUsers } from './getBlockedUsers'; -export { default as getNicknameExists } from './getNicknameExists'; -export { default as postBlockUser } from './postBlockUser'; +export { usersApi } from "./api"; +export { default as deleteUnblockUser } from "./deleteUnblockUser"; +export { default as getBlockedUsers } from "./getBlockedUsers"; +export { default as getNicknameExists } from "./getNicknameExists"; +export { default as postBlockUser } from "./postBlockUser"; diff --git a/src/apis/users/postBlockUser.ts b/src/apis/users/postBlockUser.ts index 11b44fe8..027b49e0 100644 --- a/src/apis/users/postBlockUser.ts +++ b/src/apis/users/postBlockUser.ts @@ -1,6 +1,8 @@ import { AxiosError } from "axios"; + +import { BlockUserRequest, BlockUserResponse, usersApi } from "./api"; + import { useMutation } from "@tanstack/react-query"; -import { usersApi, BlockUserResponse, BlockUserRequest } from "./api"; const usePostBlockUser = () => { return useMutation({ @@ -8,4 +10,4 @@ const usePostBlockUser = () => { }); }; -export default usePostBlockUser; \ No newline at end of file +export default usePostBlockUser; From e3f221e672cc13957ee02ca63841c89cc5d4eff2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 23:40:48 +0900 Subject: [PATCH 10/11] fix: resolve server component import issues and React Hooks violations --- src/apis/mentor/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apis/mentor/api.ts b/src/apis/mentor/api.ts index 0e312655..e85a6baa 100644 --- a/src/apis/mentor/api.ts +++ b/src/apis/mentor/api.ts @@ -16,8 +16,9 @@ export const MentorQueryKeys = { } as const; // Re-export types -export type { MentorCardPreview, MentorCardDetail, MentoringItem, MentoringApprovalStatus }; +export type { MentorCardPreview, MentorCardDetail, MentoringItem }; export type { MentoringListItem, VerifyStatus }; +export { MentoringApprovalStatus }; // Response types export interface MentoringListResponse { From 8078dc14470bb7fedc24a4e8bc3fafbad3e281c6 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 28 Dec 2025 23:57:08 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix=20:=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/Auth/api.ts | 2 +- src/apis/Auth/postRefreshToken.ts | 4 ++-- src/apis/MyPage/getProfile.ts | 2 +- src/apis/applications/api.ts | 16 ++++++++++++++++ src/apis/community/api.ts | 8 ++++---- src/apis/community/getBoard.ts | 2 +- src/apis/community/patchUpdateComment.ts | 6 +++--- src/apis/community/server.ts | 2 +- src/apis/image-upload/postUploadGpaReport.ts | 6 +++--- .../postUploadLanguageTestReport.ts | 6 +++--- .../image-upload/postUploadProfileImage.ts | 6 +++--- src/apis/mentor/api.ts | 19 +++++++++++++++++++ .../mentor/getUnconfirmedMentoringCount.ts | 4 ++-- .../mentor/legacy/patchConfirmMentoring.ts | 13 ------------- src/apis/universities/api.ts | 4 ++-- src/apis/users/api.ts | 2 ++ src/app/my/_ui/MyProfileContent/index.tsx | 4 ++-- src/app/my/match/_ui/MatchContent/index.tsx | 4 ++-- 18 files changed, 67 insertions(+), 43 deletions(-) delete mode 100644 src/apis/mentor/legacy/patchConfirmMentoring.ts diff --git a/src/apis/Auth/api.ts b/src/apis/Auth/api.ts index dd7c87f9..1dadaf97 100644 --- a/src/apis/Auth/api.ts +++ b/src/apis/Auth/api.ts @@ -82,7 +82,7 @@ export interface SignUpRequest { signUpToken: string; nickname: string; profileImageUrl: string; - preparationStage: string; + preparationStatus: string; interestedRegions: string[]; interestedCountries: string[]; } diff --git a/src/apis/Auth/postRefreshToken.ts b/src/apis/Auth/postRefreshToken.ts index ba38a805..95140426 100644 --- a/src/apis/Auth/postRefreshToken.ts +++ b/src/apis/Auth/postRefreshToken.ts @@ -5,8 +5,8 @@ import { RefreshTokenRequest, RefreshTokenResponse, authApi } from "./api"; import { useMutation } from "@tanstack/react-query"; const usePostRefreshToken = () => { - return useMutation({ - mutationFn: (data) => authApi.postRefreshToken({ data }), + return useMutation({ + mutationFn: () => authApi.postRefreshToken(), }); }; diff --git a/src/apis/MyPage/getProfile.ts b/src/apis/MyPage/getProfile.ts index b3e47b09..b25e2092 100644 --- a/src/apis/MyPage/getProfile.ts +++ b/src/apis/MyPage/getProfile.ts @@ -33,7 +33,7 @@ const useGetMyInfo = (): UseGetMyInfoResult => { const displayData = isOptimistic && queryResult.data ? { ...queryResult.data, ...pendingData } : queryResult.data; - return { ...queryResult, data: displayData }; + return { ...queryResult, data: displayData as MyInfoResponse | undefined }; }; export default useGetMyInfo; diff --git a/src/apis/applications/api.ts b/src/apis/applications/api.ts index c8b0917a..f41225aa 100644 --- a/src/apis/applications/api.ts +++ b/src/apis/applications/api.ts @@ -24,6 +24,14 @@ export interface UseSubmitApplicationRequest { }; } +export interface CompetitorsResponse { + competitors: Array<{ + id: number; + name: string; + score: number; + }>; +} + // ====== API Functions ====== export const applicationsApi = { /** @@ -41,4 +49,12 @@ export const applicationsApi = { ): Promise> => { return axiosInstance.post("/applications", request); }, + + /** + * 경쟁자 목록 조회 + */ + getCompetitors: async (config?: { params?: Record }): Promise => { + const res = await axiosInstance.get("/applications/competitors", config); + return res.data; + }, }; diff --git a/src/apis/community/api.ts b/src/apis/community/api.ts index ef780017..f57a3851 100644 --- a/src/apis/community/api.ts +++ b/src/apis/community/api.ts @@ -39,10 +39,10 @@ export interface BoardResponseItem { } export interface BoardResponse { - 0: BoardResponse0; - 1: BoardResponse1; - 2: BoardResponse2; - 3: BoardResponse3; + 0: BoardResponseItem[]; + 1: BoardResponseItem[]; + 2: BoardResponseItem[]; + 3: BoardResponseItem[]; } // Delete response types diff --git a/src/apis/community/getBoard.ts b/src/apis/community/getBoard.ts index b5922eaf..27670287 100644 --- a/src/apis/community/getBoard.ts +++ b/src/apis/community/getBoard.ts @@ -8,7 +8,7 @@ import { useQuery } from "@tanstack/react-query"; const useGetBoard = (boardCode: string | number, params?: Record) => { return useQuery({ queryKey: [QueryKeys.community.board, boardCode, params], - queryFn: () => communityApi.getBoard({ boardCode, params }), + queryFn: () => communityApi.getBoard(boardCode as string, params), enabled: !!boardCode, }); }; diff --git a/src/apis/community/patchUpdateComment.ts b/src/apis/community/patchUpdateComment.ts index 1ebe8d06..c3416453 100644 --- a/src/apis/community/patchUpdateComment.ts +++ b/src/apis/community/patchUpdateComment.ts @@ -1,12 +1,12 @@ import { AxiosError } from "axios"; -import { UpdateCommentRequest, UpdateCommentResponse, communityApi } from "./api"; +import { CommentIdResponse, communityApi } from "./api"; import { useMutation } from "@tanstack/react-query"; const usePatchUpdateComment = () => { - return useMutation({ - mutationFn: (variables) => communityApi.patchUpdateComment(variables), + return useMutation({ + mutationFn: ({ commentId, content }) => communityApi.updateComment(commentId, { content }), }); }; diff --git a/src/apis/community/server.ts b/src/apis/community/server.ts index 7e476a6d..91da59e3 100644 --- a/src/apis/community/server.ts +++ b/src/apis/community/server.ts @@ -31,7 +31,7 @@ export const getPostListServer = async ({ return serverFetch(url, { method: "GET", next: { - revalidate, + ...(revalidate !== false && { revalidate }), tags: [`posts-${boardCode}`], }, }); diff --git a/src/apis/image-upload/postUploadGpaReport.ts b/src/apis/image-upload/postUploadGpaReport.ts index aeab6da2..f8ffa4c8 100644 --- a/src/apis/image-upload/postUploadGpaReport.ts +++ b/src/apis/image-upload/postUploadGpaReport.ts @@ -1,12 +1,12 @@ import { AxiosError } from "axios"; -import { UploadGpaReportRequest, UploadGpaReportResponse, imageUploadApi } from "./api"; +import { UploadGpaReportResponse, imageUploadApi } from "./api"; import { useMutation } from "@tanstack/react-query"; const usePostUploadGpaReport = () => { - return useMutation({ - mutationFn: (data) => imageUploadApi.postUploadGpaReport({ data }), + return useMutation({ + mutationFn: (file) => imageUploadApi.postUploadGpaReport(file), }); }; diff --git a/src/apis/image-upload/postUploadLanguageTestReport.ts b/src/apis/image-upload/postUploadLanguageTestReport.ts index 47837e87..27002711 100644 --- a/src/apis/image-upload/postUploadLanguageTestReport.ts +++ b/src/apis/image-upload/postUploadLanguageTestReport.ts @@ -1,12 +1,12 @@ import { AxiosError } from "axios"; -import { UploadLanguageTestReportRequest, UploadLanguageTestReportResponse, imageUploadApi } from "./api"; +import { UploadLanguageTestReportResponse, imageUploadApi } from "./api"; import { useMutation } from "@tanstack/react-query"; const usePostUploadLanguageTestReport = () => { - return useMutation({ - mutationFn: (data) => imageUploadApi.postUploadLanguageTestReport({ data }), + return useMutation({ + mutationFn: (file) => imageUploadApi.postUploadLanguageTestReport(file), }); }; diff --git a/src/apis/image-upload/postUploadProfileImage.ts b/src/apis/image-upload/postUploadProfileImage.ts index fe738c9f..06a34553 100644 --- a/src/apis/image-upload/postUploadProfileImage.ts +++ b/src/apis/image-upload/postUploadProfileImage.ts @@ -1,12 +1,12 @@ import { AxiosError } from "axios"; -import { UploadProfileImageRequest, UploadProfileImageResponse, imageUploadApi } from "./api"; +import { UploadProfileImageResponse, imageUploadApi } from "./api"; import { useMutation } from "@tanstack/react-query"; const usePostUploadProfileImage = () => { - return useMutation({ - mutationFn: (data) => imageUploadApi.postUploadProfileImage({ data }), + return useMutation({ + mutationFn: (file) => imageUploadApi.postUploadProfileImage(file), }); }; diff --git a/src/apis/mentor/api.ts b/src/apis/mentor/api.ts index e85a6baa..6a8ee150 100644 --- a/src/apis/mentor/api.ts +++ b/src/apis/mentor/api.ts @@ -40,6 +40,12 @@ export interface MentorListResponse { content: MentorCardDetail[]; } +export interface MatchedMentorsResponse { + content: MentorCardDetail[]; + nextPageNumber: number; + totalElements: number; +} + export interface PatchApprovalStatusRequest { status: MentoringApprovalStatus; mentoringId: number; @@ -172,4 +178,17 @@ export const mentorApi = { const res = await axiosInstance.get(`/mentors/${mentorId}`); return res.data; }, + + getMatchedMentors: async (params: { + defaultSize: string | number; + defaultPage: string | number; + params?: Record; + }): Promise => { + const { defaultSize, defaultPage, params: queryParams } = params; + const res = await axiosInstance.get( + `/mentors/matched?size=${defaultSize}&page=${defaultPage}`, + { params: queryParams }, + ); + return res.data; + }, }; diff --git a/src/apis/mentor/getUnconfirmedMentoringCount.ts b/src/apis/mentor/getUnconfirmedMentoringCount.ts index b3fa51c5..9a1d4cc5 100644 --- a/src/apis/mentor/getUnconfirmedMentoringCount.ts +++ b/src/apis/mentor/getUnconfirmedMentoringCount.ts @@ -1,6 +1,6 @@ import { AxiosError } from "axios"; -import { MentorQueryKeys, mentorApi } from "./api"; +import { GetMentoringNewCountResponse, MentorQueryKeys, mentorApi } from "./api"; import { useQuery } from "@tanstack/react-query"; @@ -8,7 +8,7 @@ import { useQuery } from "@tanstack/react-query"; * @description 미확인 멘토링 수 조회 훅 */ const useGetMentoringUncheckedCount = (isEnable: boolean) => { - return useQuery({ + return useQuery({ queryKey: [MentorQueryKeys.mentoringNewCount], queryFn: mentorApi.getMentoringUncheckedCount, enabled: isEnable, diff --git a/src/apis/mentor/legacy/patchConfirmMentoring.ts b/src/apis/mentor/legacy/patchConfirmMentoring.ts deleted file mode 100644 index deb30c75..00000000 --- a/src/apis/mentor/legacy/patchConfirmMentoring.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AxiosError } from "axios"; - -import { ConfirmMentoringRequest, ConfirmMentoringResponse, mentorApi } from "./api"; - -import { useMutation } from "@tanstack/react-query"; - -const usePatchConfirmMentoring = () => { - return useMutation({ - mutationFn: (data) => mentorApi.patchConfirmMentoring({ data }), - }); -}; - -export default usePatchConfirmMentoring; diff --git a/src/apis/universities/api.ts b/src/apis/universities/api.ts index 9ee62b56..3a8b70aa 100644 --- a/src/apis/universities/api.ts +++ b/src/apis/universities/api.ts @@ -39,8 +39,8 @@ export interface WishListResponseItemLanguageRequirementsItem { } export interface WishListResponse { - 0: WishListResponse0; - 1: WishListResponse1; + 0: WishListResponseItem[]; + 1: WishListResponseItem[]; } export type WishResponse = void; diff --git a/src/apis/users/api.ts b/src/apis/users/api.ts index 15ce5719..5c69840d 100644 --- a/src/apis/users/api.ts +++ b/src/apis/users/api.ts @@ -8,6 +8,8 @@ export type BlockUserResponse = void; export type BlockUserRequest = Record; +export type UnblockUserRequest = Record; + export type UnblockUserResponse = void; export interface BlockedUsersResponseContentItem { diff --git a/src/app/my/_ui/MyProfileContent/index.tsx b/src/app/my/_ui/MyProfileContent/index.tsx index cacbc1bc..e1490448 100644 --- a/src/app/my/_ui/MyProfileContent/index.tsx +++ b/src/app/my/_ui/MyProfileContent/index.tsx @@ -9,7 +9,7 @@ import ProfileWithBadge from "@/components/ui/ProfileWithBadge"; import { UserRole } from "@/types/mentor"; import { useDeleteUserAccount, usePostLogout } from "@/apis/Auth"; -import { useGetMyInfo } from "@/apis/MyPage"; +import { MyInfoResponse, useGetMyInfo } from "@/apis/MyPage"; import { toast } from "@/lib/zustand/useToastStore"; import { IconLikeFill } from "@/public/svgs/mentor"; import { @@ -25,7 +25,7 @@ import { const NEXT_PUBLIC_CONTACT_LINK = process.env.NEXT_PUBLIC_CONTACT_LINK; const MyProfileContent = () => { - const { data: profileData = {} } = useGetMyInfo(); + const { data: profileData = {} as MyInfoResponse } = useGetMyInfo(); const { mutate: deleteUserAccount } = useDeleteUserAccount(); const { mutate: postLogout } = usePostLogout(); diff --git a/src/app/my/match/_ui/MatchContent/index.tsx b/src/app/my/match/_ui/MatchContent/index.tsx index b34f52d5..9cc2655a 100644 --- a/src/app/my/match/_ui/MatchContent/index.tsx +++ b/src/app/my/match/_ui/MatchContent/index.tsx @@ -7,11 +7,11 @@ import MentorChatCard from "@/components/mentor/MentorChatCard"; import { UserRole } from "@/types/mentor"; -import { useGetMyInfo } from "@/apis/MyPage"; +import { MyInfoResponse, useGetMyInfo } from "@/apis/MyPage"; import { useGetChatRooms } from "@/apis/chat"; const MatchContent = () => { - const { data: myInfo = {} } = useGetMyInfo(); + const { data: myInfo = {} as MyInfoResponse } = useGetMyInfo(); const { data: chatRoom = [] } = useGetChatRooms(); const isAdmin = myInfo.role === UserRole.ADMIN;