From b492e2e702268ca26bb75a8ba389fa5da9006031 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 17:57:08 +0900 Subject: [PATCH 01/19] =?UTF-8?q?refactor:=20hooks=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EB=B3=84=EB=A1=9C=20=EC=9E=AC=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mixpanel 관련 훅을 hooks/Mixpanel/ 폴더로 이동 - Scroll 관련 훅을 hooks/Scroll/ 폴더로 이동 - Application 관련 훅을 hooks/Application/ 폴더로 이동 - useValidateAnswers는 순수 함수이므로 utils로 이동 - 모든 import 경로 업데이트 --- frontend/src/App.tsx | 2 +- frontend/src/components/common/Header/Header.tsx | 4 ++-- .../components/common/Header/admin/AdminProfile.tsx | 2 +- .../common/ScrollToTopButton/ScrollToTopButton.tsx | 2 +- frontend/src/hooks/{ => Application}/useAnswers.ts | 0 frontend/src/hooks/Header/useHeaderNavigation.ts | 2 +- .../src/hooks/{ => Mixpanel}/useMixpanelTrack.ts | 0 .../src/hooks/{ => Mixpanel}/useTrackPageView.ts | 0 frontend/src/hooks/PhotoList/usePhotoNavigation.ts | 2 +- frontend/src/hooks/{ => Scroll}/ScrollToTop.tsx | 0 .../src/hooks/{ => Scroll}/useScrollDetection.ts | 0 frontend/src/hooks/{ => Scroll}/useScrollTrigger.ts | 0 .../src/hooks/{__tests__ => }/useNavigator.test.ts | 2 +- frontend/src/pages/AdminPage/AdminPage.tsx | 2 +- .../src/pages/AdminPage/auth/LoginTab/LoginTab.tsx | 4 ++-- .../components/ClubCoverEditor/ClubCoverEditor.tsx | 4 ++-- .../components/ClubLogoEditor/ClubLogoEditor.tsx | 4 ++-- .../pages/AdminPage/components/SideBar/SideBar.tsx | 2 +- .../AdminPage/tabs/AccountEditTab/AccountEditTab.tsx | 4 ++-- .../ApplicantDetailPage/ApplicantDetailPage.tsx | 4 ++-- .../ApplicantsListTab/ApplicantsListTab.tsx | 4 ++-- .../AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx | 6 +++--- .../tabs/ApplicationEditTab/ApplicationEditTab.tsx | 2 +- .../tabs/ApplicationListTab/ApplicationListTab.tsx | 6 +++--- .../tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | 6 +++--- .../ClubInfoEditTab/components/MakeTags/MakeTags.tsx | 2 +- .../components/SelectTags/SelectTags.tsx | 2 +- .../tabs/ClubIntroEditTab/ClubIntroEditTab.tsx | 6 +++--- .../AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx | 6 +++--- .../AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx | 6 +++--- .../RecruitEditTab/components/Calendar/Calendar.tsx | 2 +- .../components/MarkdownEditor/MarkdownEditor.tsx | 2 +- .../ApplicationFormPage/ApplicationFormPage.tsx | 12 ++++++------ frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx | 6 +++--- .../components/ClubApplyButton/ClubApplyButton.tsx | 4 ++-- .../components/ClubIntroContent/ClubIntroContent.tsx | 2 +- .../components/ClubProfileCard/ClubProfileCard.tsx | 2 +- .../components/ShareButton/ShareButton.tsx | 4 ++-- frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx | 2 +- frontend/src/pages/IntroducePage/IntroducePage.tsx | 2 +- frontend/src/pages/MainPage/MainPage.tsx | 4 ++-- .../src/pages/MainPage/components/Banner/Banner.tsx | 2 +- .../CategoryButtonList/CategoryButtonList.tsx | 2 +- .../src/pages/MainPage/components/Popup/Popup.tsx | 2 +- .../MainPage/components/SearchBox/SearchBox.tsx | 2 +- .../StatusRadioButton/StatusRadioButton.tsx | 2 +- frontend/src/{hooks => utils}/useValidateAnswers.ts | 0 47 files changed, 69 insertions(+), 69 deletions(-) rename frontend/src/hooks/{ => Application}/useAnswers.ts (100%) rename frontend/src/hooks/{ => Mixpanel}/useMixpanelTrack.ts (100%) rename frontend/src/hooks/{ => Mixpanel}/useTrackPageView.ts (100%) rename frontend/src/hooks/{ => Scroll}/ScrollToTop.tsx (100%) rename frontend/src/hooks/{ => Scroll}/useScrollDetection.ts (100%) rename frontend/src/hooks/{ => Scroll}/useScrollTrigger.ts (100%) rename frontend/src/hooks/{__tests__ => }/useNavigator.test.ts (98%) rename frontend/src/{hooks => utils}/useValidateAnswers.ts (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dca50f26f..9823c2b02 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,7 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ThemeProvider } from 'styled-components'; import { ScrollToTopButton } from '@/components/common/ScrollToTopButton/ScrollToTopButton'; import { AdminClubProvider } from '@/context/AdminClubContext'; -import { ScrollToTop } from '@/hooks/ScrollToTop'; +import { ScrollToTop } from '@/hooks/Scroll/ScrollToTop'; import LoginTab from '@/pages/AdminPage/auth/LoginTab/LoginTab'; import PrivateRoute from '@/pages/AdminPage/auth/PrivateRoute/PrivateRoute'; import ClubDetailPage from '@/pages/ClubDetailPage/ClubDetailPage'; diff --git a/frontend/src/components/common/Header/Header.tsx b/frontend/src/components/common/Header/Header.tsx index 0ef591969..4bf4c9c06 100644 --- a/frontend/src/components/common/Header/Header.tsx +++ b/frontend/src/components/common/Header/Header.tsx @@ -5,8 +5,8 @@ import DesktopMainIcon from '@/assets/images/moadong_name_logo.svg'; import AdminProfile from '@/components/common/Header/admin/AdminProfile'; import { USER_EVENT } from '@/constants/eventName'; import useHeaderNavigation from '@/hooks/Header/useHeaderNavigation'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import { useScrollDetection } from '@/hooks/useScrollDetection'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import { useScrollDetection } from '@/hooks/Scroll/useScrollDetection'; import SearchBox from '@/pages/MainPage/components/SearchBox/SearchBox'; import * as Styled from './Header.styles'; diff --git a/frontend/src/components/common/Header/admin/AdminProfile.tsx b/frontend/src/components/common/Header/admin/AdminProfile.tsx index 363c6a408..26c044fb3 100644 --- a/frontend/src/components/common/Header/admin/AdminProfile.tsx +++ b/frontend/src/components/common/Header/admin/AdminProfile.tsx @@ -1,6 +1,6 @@ import DefaultMoadongLogo from '@/assets/images/logos/default_profile_image.svg'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; import * as Styled from '../Header.styles'; const AdminProfile = () => { diff --git a/frontend/src/components/common/ScrollToTopButton/ScrollToTopButton.tsx b/frontend/src/components/common/ScrollToTopButton/ScrollToTopButton.tsx index 2ba34bbbc..615353f89 100644 --- a/frontend/src/components/common/ScrollToTopButton/ScrollToTopButton.tsx +++ b/frontend/src/components/common/ScrollToTopButton/ScrollToTopButton.tsx @@ -1,5 +1,5 @@ import scrollButtonIcon from '@/assets/images/icons/scroll_icon.svg'; -import { useScrollTrigger } from '@/hooks/useScrollTrigger'; +import { useScrollTrigger } from '@/hooks/Scroll/useScrollTrigger'; import * as Styled from './ScrollToTopButton.styles'; export const ScrollToTopButton = () => { diff --git a/frontend/src/hooks/useAnswers.ts b/frontend/src/hooks/Application/useAnswers.ts similarity index 100% rename from frontend/src/hooks/useAnswers.ts rename to frontend/src/hooks/Application/useAnswers.ts diff --git a/frontend/src/hooks/Header/useHeaderNavigation.ts b/frontend/src/hooks/Header/useHeaderNavigation.ts index 4b58760cf..6abfdfdee 100644 --- a/frontend/src/hooks/Header/useHeaderNavigation.ts +++ b/frontend/src/hooks/Header/useHeaderNavigation.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { useSearchStore } from '@/store/useSearchStore'; const useHeaderNavigation = () => { diff --git a/frontend/src/hooks/useMixpanelTrack.ts b/frontend/src/hooks/Mixpanel/useMixpanelTrack.ts similarity index 100% rename from frontend/src/hooks/useMixpanelTrack.ts rename to frontend/src/hooks/Mixpanel/useMixpanelTrack.ts diff --git a/frontend/src/hooks/useTrackPageView.ts b/frontend/src/hooks/Mixpanel/useTrackPageView.ts similarity index 100% rename from frontend/src/hooks/useTrackPageView.ts rename to frontend/src/hooks/Mixpanel/useTrackPageView.ts diff --git a/frontend/src/hooks/PhotoList/usePhotoNavigation.ts b/frontend/src/hooks/PhotoList/usePhotoNavigation.ts index ed63cc30c..32b995ed8 100644 --- a/frontend/src/hooks/PhotoList/usePhotoNavigation.ts +++ b/frontend/src/hooks/PhotoList/usePhotoNavigation.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect } from 'react'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '../useMixpanelTrack'; +import useMixpanelTrack from '../Mixpanel/useMixpanelTrack'; export const usePhotoNavigation = ({ currentIndex, diff --git a/frontend/src/hooks/ScrollToTop.tsx b/frontend/src/hooks/Scroll/ScrollToTop.tsx similarity index 100% rename from frontend/src/hooks/ScrollToTop.tsx rename to frontend/src/hooks/Scroll/ScrollToTop.tsx diff --git a/frontend/src/hooks/useScrollDetection.ts b/frontend/src/hooks/Scroll/useScrollDetection.ts similarity index 100% rename from frontend/src/hooks/useScrollDetection.ts rename to frontend/src/hooks/Scroll/useScrollDetection.ts diff --git a/frontend/src/hooks/useScrollTrigger.ts b/frontend/src/hooks/Scroll/useScrollTrigger.ts similarity index 100% rename from frontend/src/hooks/useScrollTrigger.ts rename to frontend/src/hooks/Scroll/useScrollTrigger.ts diff --git a/frontend/src/hooks/__tests__/useNavigator.test.ts b/frontend/src/hooks/useNavigator.test.ts similarity index 98% rename from frontend/src/hooks/__tests__/useNavigator.test.ts rename to frontend/src/hooks/useNavigator.test.ts index 3177caa5a..0c3c18cd3 100644 --- a/frontend/src/hooks/__tests__/useNavigator.test.ts +++ b/frontend/src/hooks/useNavigator.test.ts @@ -1,6 +1,6 @@ import { useNavigate } from 'react-router-dom'; import { renderHook, RenderHookResult } from '@testing-library/react'; -import useNavigator from '../useNavigator'; +import useNavigator from '@/hooks/useNavigator'; jest.mock('react-router-dom', () => ({ useNavigate: jest.fn(), diff --git a/frontend/src/pages/AdminPage/AdminPage.tsx b/frontend/src/pages/AdminPage/AdminPage.tsx index 2755c0aa6..6c6c0e44c 100644 --- a/frontend/src/pages/AdminPage/AdminPage.tsx +++ b/frontend/src/pages/AdminPage/AdminPage.tsx @@ -1,7 +1,7 @@ import { Outlet } from 'react-router-dom'; import Header from '@/components/common/Header/Header'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; import SideBar from '@/pages/AdminPage/components/SideBar/SideBar'; import * as Styled from './AdminPage.styles'; diff --git a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx index ef9298d97..95e76bf23 100644 --- a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx +++ b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx @@ -6,8 +6,8 @@ import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import useAuth from '@/hooks/useAuth'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import * as Styled from './LoginTab.styles'; import Header from '@/components/common/Header/Header'; diff --git a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx index b685bbb68..5a803c38d 100644 --- a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx @@ -6,8 +6,8 @@ import { useAdminClubContext } from '@/context/AdminClubContext'; import { useDeleteCover, useUploadCover, -} from '@/hooks/queries/club/cover/useCoverMutation'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +} from '@/hooks/Queries/club/cover/useCoverMutation'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './ClubCoverEditor.styles'; interface ClubCoverEditorProps { diff --git a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx index e75f65ca4..845c90c5c 100644 --- a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx @@ -6,8 +6,8 @@ import { useAdminClubContext } from '@/context/AdminClubContext'; import { useDeleteLogo, useUploadLogo, -} from '@/hooks/queries/club/images/useLogoMutation'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +} from '@/hooks/Queries/club/images/useLogoMutation'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './ClubLogoEditor.styles'; interface ClubLogoEditorProps { diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index 6be53880a..7d648f7d7 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { logout } from '@/apis/auth/logout'; import { ADMIN_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './SideBar.styles'; interface TabItem { diff --git a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx index 712867c80..ae6c9b66f 100644 --- a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx @@ -3,8 +3,8 @@ import { changePassword } from '@/apis/auth/changePassword'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import * as Styled from './AccountEditTab.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx index 798256a14..f594a396f 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx @@ -6,8 +6,8 @@ import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { AVAILABLE_STATUSES } from '@/constants/status'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplicant'; -import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; +import { useUpdateApplicant } from '@/hooks/Queries/applicants/useUpdateApplicant'; +import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; import { ApplicationStatus } from '@/types/applicants'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index 423cc88bb..69baa6cef 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -6,8 +6,8 @@ import { updateApplicationStatus } from '@/apis/application/updateApplication'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; -import { useDeleteApplication } from '@/hooks/queries/application/useDeleteApplication'; -import { useGetApplicationlist } from '@/hooks/queries/application/useGetApplicationlist'; +import { useDeleteApplication } from '@/hooks/Queries/application/useDeleteApplication'; +import { useGetApplicationlist } from '@/hooks/Queries/application/useGetApplicationlist'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import * as Styled from '@/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index 1a0319c39..e6c20725a 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -7,9 +7,9 @@ import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDow import SearchField from '@/components/common/SearchField/SearchField'; import { AVAILABLE_STATUSES } from '@/constants/status'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useDeleteApplicants } from '@/hooks/queries/applicants/useDeleteApplicants'; -import { useGetApplicants } from '@/hooks/queries/applicants/useGetApplicants'; -import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplicant'; +import { useDeleteApplicants } from '@/hooks/Queries/applicants/useDeleteApplicants'; +import { useGetApplicants } from '@/hooks/Queries/applicants/useGetApplicants'; +import { useUpdateApplicant } from '@/hooks/Queries/applicants/useUpdateApplicant'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { Applicant, ApplicationStatus } from '@/types/applicants'; import mapStatusToGroup from '@/utils/mapStatusToGroup'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx index b9b06f27d..5b913867b 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx @@ -8,7 +8,7 @@ import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; import INITIAL_FORM_DATA from '@/constants/INITIAL_FORM_DATA'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; +import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; import QuestionBuilder from '@/pages/AdminPage/components/QuestionBuilder/QuestionBuilder'; import { PageContainer } from '@/styles/PageContainer.styles'; import { diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx index 696bae497..ead69d188 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx @@ -6,9 +6,9 @@ import { updateApplicationStatus } from '@/apis/application/updateApplication'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; -import { useDeleteApplication } from '@/hooks/queries/application/useDeleteApplication'; -import { useDuplicateApplication } from '@/hooks/queries/application/useDuplicateApplication'; -import { useGetApplicationlist } from '@/hooks/queries/application/useGetApplicationlist'; +import { useDeleteApplication } from '@/hooks/Queries/application/useDeleteApplication'; +import { useDuplicateApplication } from '@/hooks/Queries/application/useDuplicateApplication'; +import { useGetApplicationlist } from '@/hooks/Queries/application/useGetApplicationlist'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { ApplicationFormItem, SemesterGroup } from '@/types/application'; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx index e748b6168..84f1eaf82 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx @@ -5,9 +5,9 @@ import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import { SNS_CONFIG } from '@/constants/snsConfig'; -import { useUpdateClubDetail } from '@/hooks/queries/club/useUpdateClubDetail'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import { useUpdateClubDetail } from '@/hooks/Queries/club/useUpdateClubDetail'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import ClubCoverEditor from '@/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor'; import ClubLogoEditor from '@/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx index a6f56a742..648e95678 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/MakeTags/MakeTags.tsx @@ -1,6 +1,6 @@ import deleteButton from '@/assets/images/icons/delete_button_icon.svg'; import { ADMIN_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './MakeTags.styles'; interface MakeTagsProps { diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/SelectTags/SelectTags.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/SelectTags/SelectTags.tsx index a16ec7cb3..22f121db1 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/SelectTags/SelectTags.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/SelectTags/SelectTags.tsx @@ -1,5 +1,5 @@ import { ADMIN_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './SelectTags.styles'; export interface TagOption { diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx index 09b58222c..e57d1ed19 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx @@ -4,9 +4,9 @@ import { useQueryClient } from '@tanstack/react-query'; import Button from '@/components/common/Button/Button'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDetail } from '@/hooks/queries/club/useUpdateClubDetail'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import { useUpdateClubDetail } from '@/hooks/Queries/club/useUpdateClubDetail'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { Award, ClubDetail, FAQ, IdealCandidate } from '@/types/club'; import * as Styled from './ClubIntroEditTab.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index 587b319f7..e4b8db386 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -6,9 +6,9 @@ import { MAX_FILE_COUNT, MAX_FILE_SIZE } from '@/constants/uploadLimit'; import { useUpdateFeed, useUploadFeed, -} from '@/hooks/queries/club/images/useFeedMutation'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +} from '@/hooks/Queries/club/images/useFeedMutation'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { ImagePreview } from '@/pages/AdminPage/tabs/PhotoEditTab/components/ImagePreview/ImagePreview'; import { ClubDetail } from '@/types/club'; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx index 9464d3883..955e8de74 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx @@ -5,9 +5,9 @@ import { setYear } from 'date-fns'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDescription } from '@/hooks/queries/club/useUpdateClubDescription'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import { useUpdateClubDescription } from '@/hooks/Queries/club/useUpdateClubDescription'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import Calendar from '@/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar'; import { ClubDetail } from '@/types/club'; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx index 0184ba715..57c7d097d 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx @@ -3,7 +3,7 @@ import { ko } from 'date-fns/locale'; import 'react-datepicker/dist/react-datepicker.css'; import DatePicker, { ReactDatePickerCustomHeaderProps } from 'react-datepicker'; import { ADMIN_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './Calendar.styles'; interface CalendarProps { diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/MarkdownEditor/MarkdownEditor.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/MarkdownEditor/MarkdownEditor.tsx index 3e5fd33fb..e1f8bbc23 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/MarkdownEditor/MarkdownEditor.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/MarkdownEditor/MarkdownEditor.tsx @@ -4,7 +4,7 @@ import remarkGfm from 'remark-gfm'; import eye_icon from '@/assets/images/icons/eye_icon.svg'; import pencil_icon from '@/assets/images/icons/pencil_icon_1.svg'; import { ADMIN_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './MarkdownEditor.styles'; interface MarkdownEditorProps { diff --git a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx index c66e24194..9efe0384e 100644 --- a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx +++ b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx @@ -4,12 +4,12 @@ import applyToClub from '@/apis/application/applyToClub'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; -import { useAnswers } from '@/hooks/useAnswers'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; -import { validateAnswers } from '@/hooks/useValidateAnswers'; +import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useAnswers } from '@/hooks/Application/useAnswers'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { validateAnswers } from '@/utils/useValidateAnswers'; import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; import { PageContainer } from '@/styles/PageContainer.styles'; diff --git a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx index b85e3ac93..d4db00b34 100644 --- a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx +++ b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx @@ -3,10 +3,10 @@ import { useParams, useSearchParams } from 'react-router-dom'; import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; import useDevice from '@/hooks/useDevice'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import ClubFeed from '@/pages/ClubDetailPage/components/ClubFeed/ClubFeed'; import ClubIntroContent from '@/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent'; import ClubProfileCard from '@/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard'; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx index ffdf83032..8be4a6df6 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx @@ -1,8 +1,8 @@ import * as Styled from './ClubApplyButton.styles'; import { useNavigate, useParams } from 'react-router-dom'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; import getApplication from '@/apis/application/getApplication'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { USER_EVENT } from '@/constants/eventName'; import { useState } from 'react'; import { ApplicationForm, ApplicationFormMode } from '@/types/application'; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx index a3ad1ab13..93385fe02 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './ClubIntroContent.styles'; export interface Award { diff --git a/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.tsx b/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.tsx index 0579a8c50..e6ce40bd0 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.tsx @@ -6,7 +6,7 @@ import DefaultCover from '@/assets/images/logos/default_cover_image.png'; import DefaultLogo from '@/assets/images/logos/default_profile_image.svg'; import ClubStateBox from '@/components/ClubStateBox/ClubStateBox'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { SNSPlatform } from '@/types/club'; import * as Styled from './ClubProfileCard.styles'; diff --git a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx index 910561794..c05542256 100644 --- a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx @@ -1,8 +1,8 @@ import ShareIcon from '@/assets/images/icons/share_icon.svg'; import ShareIconMobile from '@/assets/images/icons/share_icon_mobile.svg'; import { USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/queries/club/useGetClubDetail'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useDevice from '@/hooks/useDevice'; import * as Styled from './ShareButton.styles'; diff --git a/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx b/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx index 5ce54883a..0691b9c42 100644 --- a/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx +++ b/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx @@ -2,7 +2,7 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import { CLUB_UNION_MEMBERS } from '@/constants/CLUB_UNION_INFO'; import { PAGE_VIEW } from '@/constants/eventName'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { PageContainer } from '@/styles/PageContainer.styles'; import isInAppWebView from '@/utils/isInAppWebView'; import * as Styled from './ClubUnionPage.styles'; diff --git a/frontend/src/pages/IntroducePage/IntroducePage.tsx b/frontend/src/pages/IntroducePage/IntroducePage.tsx index cb6a0d2fd..608f03c7b 100644 --- a/frontend/src/pages/IntroducePage/IntroducePage.tsx +++ b/frontend/src/pages/IntroducePage/IntroducePage.tsx @@ -1,7 +1,7 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import { PAGE_VIEW } from '@/constants/eventName'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import isInAppWebView from '@/utils/isInAppWebView'; import IntroSection from './components/sections/1.IntroSection/IntroSection'; import ProblemSection from './components/sections/2.ProblemSection/ProblemSection'; diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index aef79fe53..20fe5bc4b 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -3,8 +3,8 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW } from '@/constants/eventName'; -import { useGetCardList } from '@/hooks/queries/club/useGetCardList'; -import useTrackPageView from '@/hooks/useTrackPageView'; +import { useGetCardList } from '@/hooks/Queries/club/useGetCardList'; +import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import Banner from '@/pages/MainPage/components/Banner/Banner'; import CategoryButtonList from '@/pages/MainPage/components/CategoryButtonList/CategoryButtonList'; import ClubCard from '@/pages/MainPage/components/ClubCard/ClubCard'; diff --git a/frontend/src/pages/MainPage/components/Banner/Banner.tsx b/frontend/src/pages/MainPage/components/Banner/Banner.tsx index 2d5bca9cd..fb5c52311 100644 --- a/frontend/src/pages/MainPage/components/Banner/Banner.tsx +++ b/frontend/src/pages/MainPage/components/Banner/Banner.tsx @@ -6,7 +6,7 @@ import NextButton from '@/assets/images/icons/next_button_icon.svg'; import PrevButton from '@/assets/images/icons/prev_button_icon.svg'; import { USER_EVENT } from '@/constants/eventName'; import useDevice from '@/hooks/useDevice'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useNavigator from '@/hooks/useNavigator'; import { detectPlatform, getAppStoreLink } from '@/utils/appStoreLink'; import * as Styled from './Banner.styles'; diff --git a/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx b/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx index cb2cf59f0..3dbff34b9 100644 --- a/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx +++ b/frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx @@ -3,7 +3,7 @@ import { inactiveCategoryIcons, } from '@/assets/images/icons/category_button'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { useSelectedCategory } from '@/store/useCategoryStore'; import { useSearchStore } from '@/store/useSearchStore'; import * as Styled from './CategoryButtonList.styles'; diff --git a/frontend/src/pages/MainPage/components/Popup/Popup.tsx b/frontend/src/pages/MainPage/components/Popup/Popup.tsx index 16c66f519..1f7a99000 100644 --- a/frontend/src/pages/MainPage/components/Popup/Popup.tsx +++ b/frontend/src/pages/MainPage/components/Popup/Popup.tsx @@ -2,7 +2,7 @@ import { MouseEvent, useEffect, useState } from 'react'; import AppDownloadImage from '@/assets/images/popup/app-download.svg'; import { USER_EVENT } from '@/constants/eventName'; import useDevice from '@/hooks/useDevice'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { detectPlatform, getAppStoreLink } from '@/utils/appStoreLink'; import * as Styled from './Popup.styles'; diff --git a/frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx b/frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx index 8776873d0..0aa05e71e 100644 --- a/frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx +++ b/frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx @@ -1,6 +1,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import SearchField from '@/components/common/SearchField/SearchField'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { useSelectedCategory } from '@/store/useCategoryStore'; import { useSearchInput } from '@/store/useSearchStore'; diff --git a/frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx b/frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx index dc3455bc5..2b9ef69a7 100644 --- a/frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx +++ b/frontend/src/pages/MainPage/components/StatusRadioButton/StatusRadioButton.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '@/hooks/useMixpanelTrack'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './StatusRadioButton.styles'; interface StatusRadioButtonProps { diff --git a/frontend/src/hooks/useValidateAnswers.ts b/frontend/src/utils/useValidateAnswers.ts similarity index 100% rename from frontend/src/hooks/useValidateAnswers.ts rename to frontend/src/utils/useValidateAnswers.ts From 0ba9f4dea6e8af3db1f2a27fbf6c21589b29783e Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 18:18:17 +0900 Subject: [PATCH 02/19] =?UTF-8?q?refactor:=20API=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC=20=20-=2024=EA=B0=9C=20API=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=84=205=EA=B0=9C=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EB=B3=84=20=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20(applicants,=20application,=20auth,=20club,=20image?= =?UTF-8?q?)=20-=20apis/utils/apiHelpers.ts=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EA=B3=B5=ED=86=B5=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC=20-=20handl?= =?UTF-8?q?eResponse=EC=99=80=20withErrorHandling=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?-=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=84=A0=ED=83=9D=EC=A0=81=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=EC=9C=BC=EB=A1=9C=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EC=9C=A0=EC=A7=80=20-=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20API=EC=97=90=20=EC=9D=BC=EA=B4=80=EB=90=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20-=2043=EA=B0=9C=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=9D=98=20import=20=EA=B2=BD=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 24개 API 파일을 5개 도메인별 파일로 통합 (applicants, application, auth, club, image) - apis/utils/apiHelpers.ts 추가하여 공통 에러 처리 로직 분리 - handleResponse와 withErrorHandling으로 중복 코드 제거 - 커스텀 에러 메시지 선택적 지원으로 기존 동작 유지 - 모든 API에 일관된 에러 처리 패턴 적용 - 43개 파일의 import 경로 업데이트 --- frontend/src/apis/applicants.ts | 31 ++++ .../src/apis/applicants/deleteApplicants.ts | 32 ---- .../src/apis/applicants/getClubApplicants.ts | 22 --- frontend/src/apis/application.ts | 168 ++++++++++++++++++ frontend/src/apis/application/applyToClub.ts | 35 ---- .../src/apis/application/createApplication.ts | 27 --- .../src/apis/application/deleteApplication.ts | 25 --- .../apis/application/duplicateApplication.ts | 22 --- .../apis/application/getActiveApplications.ts | 20 --- .../apis/application/getAllApplications.ts | 21 --- .../src/apis/application/getApplication.ts | 32 ---- .../apis/application/getApplicationOptions.ts | 28 --- .../apis/application/updateApplicantDetail.ts | 38 ---- .../src/apis/application/updateApplication.ts | 58 ------ frontend/src/apis/auth.ts | 74 ++++++++ frontend/src/apis/auth/changePassword.ts | 23 --- frontend/src/apis/auth/getClubIdByToken.ts | 13 -- frontend/src/apis/auth/login.ts | 34 ---- frontend/src/apis/auth/logout.ts | 18 -- frontend/src/apis/auth/refreshAccessToken.ts | 2 - frontend/src/apis/club.ts | 68 +++++++ frontend/src/apis/getClubDetail.ts | 17 -- frontend/src/apis/getClubList.ts | 30 ---- frontend/src/apis/image.ts | 156 ++++++++++++++++ frontend/src/apis/image/cover.ts | 59 ------ frontend/src/apis/image/feed.ts | 50 ------ frontend/src/apis/image/logo.ts | 58 ------ frontend/src/apis/image/uploadToStorage.ts | 14 -- frontend/src/apis/updateClubDescription.ts | 37 ---- frontend/src/apis/updateClubDetail.ts | 36 ---- frontend/src/apis/utils/apiHelpers.ts | 35 ++++ .../queries/applicants/useDeleteApplicants.ts | 2 +- .../queries/applicants/useGetApplicants.ts | 2 +- .../queries/applicants/useUpdateApplicant.ts | 2 +- .../application/useDeleteApplication.ts | 2 +- .../application/useDuplicateApplication.ts | 2 +- .../queries/application/useGetApplication.ts | 2 +- .../application/useGetApplicationlist.ts | 4 +- .../queries/club/cover/useCoverMutation.ts | 3 +- .../queries/club/images/useFeedMutation.ts | 3 +- .../queries/club/images/useLogoMutation.ts | 3 +- .../src/hooks/queries/club/useGetCardList.ts | 2 +- .../hooks/queries/club/useGetClubDetail.ts | 2 +- .../queries/club/useUpdateClubDescription.ts | 2 +- .../hooks/queries/club/useUpdateClubDetail.ts | 2 +- frontend/src/hooks/useAuth.ts | 2 +- .../AdminPage/auth/LoginTab/LoginTab.tsx | 2 +- .../AdminPage/components/SideBar/SideBar.tsx | 2 +- .../tabs/AccountEditTab/AccountEditTab.tsx | 2 +- .../ApplicantsListTab/ApplicantsListTab.tsx | 2 +- .../ApplicationEditTab/ApplicationEditTab.tsx | 3 +- .../ApplicationListTab/ApplicationListTab.tsx | 2 +- .../ApplicationFormPage.tsx | 2 +- .../ClubApplyButton/ClubApplyButton.tsx | 3 +- 54 files changed, 556 insertions(+), 780 deletions(-) create mode 100644 frontend/src/apis/applicants.ts delete mode 100644 frontend/src/apis/applicants/deleteApplicants.ts delete mode 100644 frontend/src/apis/applicants/getClubApplicants.ts create mode 100644 frontend/src/apis/application.ts delete mode 100644 frontend/src/apis/application/applyToClub.ts delete mode 100644 frontend/src/apis/application/createApplication.ts delete mode 100644 frontend/src/apis/application/deleteApplication.ts delete mode 100644 frontend/src/apis/application/duplicateApplication.ts delete mode 100644 frontend/src/apis/application/getActiveApplications.ts delete mode 100644 frontend/src/apis/application/getAllApplications.ts delete mode 100644 frontend/src/apis/application/getApplication.ts delete mode 100644 frontend/src/apis/application/getApplicationOptions.ts delete mode 100644 frontend/src/apis/application/updateApplicantDetail.ts delete mode 100644 frontend/src/apis/application/updateApplication.ts create mode 100644 frontend/src/apis/auth.ts delete mode 100644 frontend/src/apis/auth/changePassword.ts delete mode 100644 frontend/src/apis/auth/getClubIdByToken.ts delete mode 100644 frontend/src/apis/auth/login.ts delete mode 100644 frontend/src/apis/auth/logout.ts create mode 100644 frontend/src/apis/club.ts delete mode 100644 frontend/src/apis/getClubDetail.ts delete mode 100644 frontend/src/apis/getClubList.ts create mode 100644 frontend/src/apis/image.ts delete mode 100644 frontend/src/apis/image/cover.ts delete mode 100644 frontend/src/apis/image/feed.ts delete mode 100644 frontend/src/apis/image/logo.ts delete mode 100644 frontend/src/apis/image/uploadToStorage.ts delete mode 100644 frontend/src/apis/updateClubDescription.ts delete mode 100644 frontend/src/apis/updateClubDetail.ts create mode 100644 frontend/src/apis/utils/apiHelpers.ts diff --git a/frontend/src/apis/applicants.ts b/frontend/src/apis/applicants.ts new file mode 100644 index 000000000..b51f08686 --- /dev/null +++ b/frontend/src/apis/applicants.ts @@ -0,0 +1,31 @@ +import API_BASE_URL from '@/constants/api'; +import { secureFetch } from './auth/secureFetch'; +import { handleResponse, withErrorHandling } from './utils/apiHelpers'; + +export const getClubApplicants = async (applicationFormId: string) => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/apply/info/${applicationFormId}`, + ); + return handleResponse(response); + }, 'Error fetching club applicants'); +}; + +export const deleteApplicants = async ( + applicantIds: string[], + applicationFormId: string, +) => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/applicant/${applicationFormId}`, + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ applicantIds: applicantIds }), + }, + ); + return handleResponse(response); + }, 'Error fetching delete applicants'); +}; diff --git a/frontend/src/apis/applicants/deleteApplicants.ts b/frontend/src/apis/applicants/deleteApplicants.ts deleted file mode 100644 index 3f4d1f878..000000000 --- a/frontend/src/apis/applicants/deleteApplicants.ts +++ /dev/null @@ -1,32 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { secureFetch } from '../auth/secureFetch'; - -const deleteApplicants = async ( - applicantIds: string[], - applicationFormId: string, -) => { - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/applicant/${applicationFormId}`, - { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ applicantIds: applicantIds }), - }, - ); - if (!response.ok) { - console.error(`Failed to fetch: ${response.statusText}`); - throw new Error((await response.json()).message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('Error fetching delete applicants', error); - throw error; - } -}; - -export default deleteApplicants; diff --git a/frontend/src/apis/applicants/getClubApplicants.ts b/frontend/src/apis/applicants/getClubApplicants.ts deleted file mode 100644 index 2b7ded337..000000000 --- a/frontend/src/apis/applicants/getClubApplicants.ts +++ /dev/null @@ -1,22 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { secureFetch } from '../auth/secureFetch'; - -const getClubApplicants = async (applicationFormId: string) => { - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/apply/info/${applicationFormId}`, - ); - if (!response.ok) { - console.error(`Failed to fetch: ${response.statusText}`); - throw new Error((await response.json()).message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('Error fetching club applicants', error); - throw error; - } -}; - -export default getClubApplicants; diff --git a/frontend/src/apis/application.ts b/frontend/src/apis/application.ts new file mode 100644 index 000000000..714146fa2 --- /dev/null +++ b/frontend/src/apis/application.ts @@ -0,0 +1,168 @@ +import API_BASE_URL from '@/constants/api'; +import { secureFetch } from './auth/secureFetch'; +import { AnswerItem, ApplicationFormData } from '@/types/application'; +import { UpdateApplicantParams } from '@/types/applicants'; +import { handleResponse, withErrorHandling } from './utils/apiHelpers'; + +export const applyToClub = async ( + clubId: string, + applicationFormId: string, + answers: AnswerItem[], +) => { + return withErrorHandling(async () => { + const response = await fetch( + `${API_BASE_URL}/api/club/${clubId}/apply/${applicationFormId}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + questions: [...answers], + }), + }, + ); + return handleResponse(response); + }, '답변 제출 중 오류 발생:'); +}; + +export const createApplication = async (data: ApplicationFormData) => { + return withErrorHandling(async () => { + const response = await secureFetch(`${API_BASE_URL}/api/club/application`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + return handleResponse(response); + }, '지원서 제출 중 오류 발생:'); +}; + +export const deleteApplication = async (applicationFormId: string) => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/application/${applicationFormId}`, + { + method: 'DELETE', + }, + ); + return handleResponse(response); + }, 'Error fetching delete application'); +}; + +export const duplicateApplication = async (applicationFormId: string) => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/application/${applicationFormId}/duplicate`, + { + method: 'POST', + }, + ); + return handleResponse(response); + }, '지원서 복제 중 오류 발생:'); +}; + +export const getActiveApplications = async (clubId: string) => { + return withErrorHandling(async () => { + const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`); + return handleResponse(response); + }, '활성화된 지원서 목록 조회 중 오류 발생:'); +}; + +export const getAllApplicationForms = async () => { + return withErrorHandling(async () => { + const response = await secureFetch(`${API_BASE_URL}/api/club/application`); + return handleResponse(response); + }, '모든 지원서 양식 조회 중 오류 발생:'); +}; + +export const getApplication = async ( + clubId: string, + applicationFormId: string, +): Promise => { + return withErrorHandling(async () => { + const response = await fetch( + `${API_BASE_URL}/api/club/${clubId}/apply/${applicationFormId}`, + ); + return handleResponse(response); + }, '지원서 조회 중 오류가 발생했습니다'); +}; + +export const getApplicationOptions = async (clubId: string) => { + return withErrorHandling(async () => { + const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`); + const data = await handleResponse(response); + + let forms: Array<{ id: string; title: string }> = []; + if (data && Array.isArray(data.forms)) { + forms = data.forms; + } + return forms; + }, '지원서 옵션 조회 중 오류가 발생했습니다.'); +}; + +export const updateApplicantDetail = async ( + applicant: UpdateApplicantParams[], + applicationFormId: string | undefined, +) => { + if (!applicationFormId) { + throw new Error( + 'applicationFormId가 존재하지 않아 지원자 정보를 수정할 수 없습니다.', + ); + } + + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/applicant/${applicationFormId}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(applicant), + }, + ); + return handleResponse(response); + }, '지원자의 지원서 정보 수정 중 오류 발생:'); +}; + +export const updateApplication = async ( + data: ApplicationFormData, + applicationFormId: string, +) => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/application/${applicationFormId}`, + { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }, + ); + return handleResponse(response); + }, '지원서 수정 중 오류 발생:'); +}; + +export const updateApplicationStatus = async ( + applicationFormId: string, + currentStatus: string, +) => { + const newStatus = currentStatus === 'ACTIVE' ? false : true; + + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/application/${applicationFormId}`, + { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ active: newStatus }), + }, + ); + return handleResponse(response); + }, '지원서 상태 수정 중 오류 발생:'); +}; diff --git a/frontend/src/apis/application/applyToClub.ts b/frontend/src/apis/application/applyToClub.ts deleted file mode 100644 index 99ed63e85..000000000 --- a/frontend/src/apis/application/applyToClub.ts +++ /dev/null @@ -1,35 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { AnswerItem } from '@/types/application'; - -export const applyToClub = async ( - clubId: string, - applicationFormId: string, - answers: AnswerItem[], -) => { - try { - const response = await fetch( - `${API_BASE_URL}/api/club/${clubId}/apply/${applicationFormId}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - questions: [...answers], - }), - }, - ); - - if (!response.ok) { - throw new Error('답변 제출에 실패했습니다.'); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('답변 제출 중 오류 발생:', error); - throw error; - } -}; - -export default applyToClub; diff --git a/frontend/src/apis/application/createApplication.ts b/frontend/src/apis/application/createApplication.ts deleted file mode 100644 index e307d84e9..000000000 --- a/frontend/src/apis/application/createApplication.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; -import { ApplicationFormData } from '@/types/application'; - -export const createApplication = async (data: ApplicationFormData) => { - try { - const response = await secureFetch(`${API_BASE_URL}/api/club/application`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - throw new Error('지원서 제출에 실패했습니다.'); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('지원서 제출 중 오류 발생:', error); - throw error; - } -}; - -export default createApplication; diff --git a/frontend/src/apis/application/deleteApplication.ts b/frontend/src/apis/application/deleteApplication.ts deleted file mode 100644 index 906b47d98..000000000 --- a/frontend/src/apis/application/deleteApplication.ts +++ /dev/null @@ -1,25 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { secureFetch } from '../auth/secureFetch'; - -const deleteApplication = async (applicationFormId: string) => { - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/application/${applicationFormId}`, - { - method: 'DELETE', - }, - ); - if (!response.ok) { - console.error(`Failed to delete: ${response.statusText}`); - throw new Error((await response.json()).message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('Error fetching delete application', error); - throw error; - } -}; - -export default deleteApplication; diff --git a/frontend/src/apis/application/duplicateApplication.ts b/frontend/src/apis/application/duplicateApplication.ts deleted file mode 100644 index 9ea9f00be..000000000 --- a/frontend/src/apis/application/duplicateApplication.ts +++ /dev/null @@ -1,22 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { secureFetch } from '../auth/secureFetch'; - -export const duplicateApplication = async (applicationFormId: string) => { - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/application/${applicationFormId}/duplicate`, - { - method: 'POST', - }, - ); - if (!response.ok) { - throw new Error('지원서 복제에 실패했습니다.'); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('지원서 복제 중 오류 발생:', error); - throw error; - } -}; diff --git a/frontend/src/apis/application/getActiveApplications.ts b/frontend/src/apis/application/getActiveApplications.ts deleted file mode 100644 index a3157c88c..000000000 --- a/frontend/src/apis/application/getActiveApplications.ts +++ /dev/null @@ -1,20 +0,0 @@ -import API_BASE_URL from '@/constants/api'; - -const getActiveApplications = async (clubId: string) => { - try { - const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`); - - if (!response.ok) { - console.error(`Failed to fetch: ${response.statusText}`); - throw new Error((await response.json()).message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('활성화된 지원서 목록 조회 중 오류 발생:', error); - throw error; - } -}; - -export default getActiveApplications; diff --git a/frontend/src/apis/application/getAllApplications.ts b/frontend/src/apis/application/getAllApplications.ts deleted file mode 100644 index 8abd2039e..000000000 --- a/frontend/src/apis/application/getAllApplications.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; - -const getAllApplicationForms = async () => { - try { - const response = await secureFetch(`${API_BASE_URL}/api/club/application`); - - if (!response.ok) { - console.error(`Failed to fetch: ${response.statusText}`); - throw new Error((await response.json()).message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('모든 지원서 양식 조회 중 오류 발생:', error); - throw error; - } -}; - -export default getAllApplicationForms; diff --git a/frontend/src/apis/application/getApplication.ts b/frontend/src/apis/application/getApplication.ts deleted file mode 100644 index e7099912b..000000000 --- a/frontend/src/apis/application/getApplication.ts +++ /dev/null @@ -1,32 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { ApplicationFormData } from '@/types/application'; - -const getApplication = async ( - clubId: string, - applicationFormId: string, -): Promise => { - try { - const response = await fetch( - `${API_BASE_URL}/api/club/${clubId}/apply/${applicationFormId}`, - ); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - if (errorData?.message) message = errorData.message; - } catch {} - console.error(`Failed to fetch: ${message}`); - throw new Error(message); - } - - const result = await response.json(); - return result.data; - } catch (error) { - // [x] FIXME: - // {"statuscode":"800-1","message":"지원서가 존재하지 않습니다.","data":null} - console.error('지원서 조회 중 오류가 발생했습니다', error); - throw error; - } -}; - -export default getApplication; diff --git a/frontend/src/apis/application/getApplicationOptions.ts b/frontend/src/apis/application/getApplicationOptions.ts deleted file mode 100644 index 2b6027165..000000000 --- a/frontend/src/apis/application/getApplicationOptions.ts +++ /dev/null @@ -1,28 +0,0 @@ -import API_BASE_URL from '@/constants/api'; - -const getApplicationOptions = async (clubId: string) => { - try { - const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`); - if (!response.ok) { - let message = response.statusText; - try { - const errorData = await response.json(); - if (errorData?.message) message = errorData.message; - } catch {} - console.error(`Failed to fetch options: ${message}`); - throw new Error(message); - } - - const result = await response.json(); - let forms: Array<{ id: string; title: string }> = []; - if (result && result.data && Array.isArray(result.data.forms)) { - forms = result.data.forms; - } - return forms; - } catch (error) { - console.error('지원서 옵션 조회 중 오류가 발생했습니다.', error); - throw error; - } -}; - -export default getApplicationOptions; diff --git a/frontend/src/apis/application/updateApplicantDetail.ts b/frontend/src/apis/application/updateApplicantDetail.ts deleted file mode 100644 index 7e6e0dee4..000000000 --- a/frontend/src/apis/application/updateApplicantDetail.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; -import { UpdateApplicantParams } from '@/types/applicants'; - -export const updateApplicantDetail = async ( - applicant: UpdateApplicantParams[], - applicationFormId: string | undefined, -) => { - if (!applicationFormId) { - throw new Error( - 'applicationFormId가 존재하지 않아 지원자 정보를 수정할 수 없습니다.', - ); - } - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/applicant/${applicationFormId}`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(applicant), - }, - ); - - if (!response.ok) { - throw new Error('지원자의 지원서 정보 수정에 실패했습니다.'); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('지원자의 지원서 정보 수정 중 오류 발생:', error); - throw error; - } -}; - -export default updateApplicantDetail; diff --git a/frontend/src/apis/application/updateApplication.ts b/frontend/src/apis/application/updateApplication.ts deleted file mode 100644 index 6a44b56ad..000000000 --- a/frontend/src/apis/application/updateApplication.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; -import { ApplicationFormData } from '@/types/application'; - -export const updateApplication = async ( - data: ApplicationFormData, - applicationFormId: string, -) => { - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/application/${applicationFormId}`, - { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }, - ); - - if (!response.ok) { - throw new Error('지원서 수정에 실패했습니다.'); - } - - const result = await response.json(); - return result.data; - } catch (error) { - console.error('지원서 수정 중 오류 발생:', error); - throw error; - } -}; - -export const updateApplicationStatus = async ( - applicationFormId: string, - currentStatus: string, -) => { - const newStatus = currentStatus === 'ACTIVE' ? false : true; - try { - const response = await secureFetch( - `${API_BASE_URL}/api/club/application/${applicationFormId}`, - { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ active: newStatus }), - }, - ); - if (!response.ok) { - throw new Error('지원서 상태 수정에 실패했습니다.'); - } - const result = await response.json(); - return result.data; - } catch (error) { - console.error('지원서 상태 수정 중 오류 발생:', error); - throw error; - } -}; diff --git a/frontend/src/apis/auth.ts b/frontend/src/apis/auth.ts new file mode 100644 index 000000000..c7b6ace98 --- /dev/null +++ b/frontend/src/apis/auth.ts @@ -0,0 +1,74 @@ +import API_BASE_URL from '@/constants/api'; +import { secureFetch } from './auth/secureFetch'; +import { handleResponse, withErrorHandling } from './utils/apiHelpers'; + +interface LoginResponseData { + accessToken: string; + clubId: string; +} + +interface ChangePasswordPayload { + password: string; +} + +export const login = async ( + userId: string, + password: string, +): Promise => { + return withErrorHandling(async () => { + const response = await fetch(`${API_BASE_URL}/auth/user/login`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ userId, password }), + }); + return handleResponse(response); + }, '로그인 중 오류 발생'); +}; + +export const logout = async (): Promise => { + return withErrorHandling(async () => { + const accessToken = localStorage.getItem('accessToken'); + + if (!accessToken) { + return; + } + + const response = await fetch(`${API_BASE_URL}/auth/user/logout`, { + method: 'GET', + credentials: 'include', + }); + + await handleResponse(response); + }, '로그아웃 중 오류 발생'); +}; + +export const getClubIdByToken = async (): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/auth/user/find/club`, + { + method: 'POST', + }, + ); + const data = await handleResponse(response); + return data.clubId; + }, 'ClubId 조회 중 오류 발생'); +}; + +export const changePassword = async ( + payload: ChangePasswordPayload, +): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch(`${API_BASE_URL}/auth/user/`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + await handleResponse(response); + }, '비밀번호 변경 중 오류 발생'); +}; diff --git a/frontend/src/apis/auth/changePassword.ts b/frontend/src/apis/auth/changePassword.ts deleted file mode 100644 index 93778e77c..000000000 --- a/frontend/src/apis/auth/changePassword.ts +++ /dev/null @@ -1,23 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { secureFetch } from './secureFetch'; - -interface ChangePasswordPayload { - password: string; -} - -export const changePassword = async ( - payload: ChangePasswordPayload, -): Promise => { - const response = await secureFetch(`${API_BASE_URL}/auth/user/`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.message || '비밀번호 변경에 실패했습니다.'); - } -}; diff --git a/frontend/src/apis/auth/getClubIdByToken.ts b/frontend/src/apis/auth/getClubIdByToken.ts deleted file mode 100644 index 4bb8cfdb0..000000000 --- a/frontend/src/apis/auth/getClubIdByToken.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; - -export const getClubIdByToken = async (): Promise => { - const response = await secureFetch(`${API_BASE_URL}/auth/user/find/club`, { - method: 'POST', - }); - - if (!response.ok) throw new Error('Unauthorized'); - - const { data } = await response.json(); - return data.clubId; -}; diff --git a/frontend/src/apis/auth/login.ts b/frontend/src/apis/auth/login.ts deleted file mode 100644 index 2bfbe73c6..000000000 --- a/frontend/src/apis/auth/login.ts +++ /dev/null @@ -1,34 +0,0 @@ -import API_BASE_URL from '@/constants/api'; - -interface LoginResponseData { - accessToken: string; - clubId: string; -} - -interface LoginResponse { - statuscode: string; - message: string; - data: LoginResponseData; -} - -export const login = async ( - userId: string, - password: string, -): Promise => { - const response = await fetch(`${API_BASE_URL}/auth/user/login`, { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ userId, password }), - }); - - if (!response.ok) { - throw new Error(`로그인에 실패하였습니다: ${response.statusText}`); - } - - const jsonResponse: LoginResponse = await response.json(); - - return jsonResponse.data; -}; diff --git a/frontend/src/apis/auth/logout.ts b/frontend/src/apis/auth/logout.ts deleted file mode 100644 index a4eae24df..000000000 --- a/frontend/src/apis/auth/logout.ts +++ /dev/null @@ -1,18 +0,0 @@ -import API_BASE_URL from '@/constants/api'; - -export const logout = async (): Promise => { - const accessToken = localStorage.getItem('accessToken'); - - if (!accessToken) { - return; - } - - const response = await fetch(`${API_BASE_URL}/auth/user/logout`, { - method: 'GET', - credentials: 'include', - }); - - if (!response.ok) { - throw new Error(`로그아웃에 실패하였습니다 ${response.statusText}`); - } -}; diff --git a/frontend/src/apis/auth/refreshAccessToken.ts b/frontend/src/apis/auth/refreshAccessToken.ts index f5830ccb8..97f054e50 100644 --- a/frontend/src/apis/auth/refreshAccessToken.ts +++ b/frontend/src/apis/auth/refreshAccessToken.ts @@ -6,12 +6,10 @@ export const refreshAccessToken = async (): Promise => { credentials: 'include', }); - // refresh 성공하여 accessToken 반환 if (res.status === 200) { const { data } = await res.json(); return data.accessToken; } - // refresh 실패 or 만료 throw new Error('REFRESH_FAILED'); }; diff --git a/frontend/src/apis/club.ts b/frontend/src/apis/club.ts new file mode 100644 index 000000000..3c0b18b0d --- /dev/null +++ b/frontend/src/apis/club.ts @@ -0,0 +1,68 @@ +import API_BASE_URL from '@/constants/api'; +import { secureFetch } from './auth/secureFetch'; +import { ClubDetail, ClubDescription } from '@/types/club'; +import { handleResponse, withErrorHandling } from './utils/apiHelpers'; + +export const getClubDetail = async (clubId: string): Promise => { + return withErrorHandling(async () => { + const response = await fetch(`${API_BASE_URL}/api/club/${clubId}`); + const data = await handleResponse(response); + return data.club; + }, 'Error fetching club details'); +}; + +export const getClubList = async ( + keyword: string = '', + recruitmentStatus: string = 'all', + category: string = 'all', + division: string = 'all', +) => { + return withErrorHandling(async () => { + const url = new URL(`${API_BASE_URL}/api/club/search/`); + const params = new URLSearchParams({ + keyword, + recruitmentStatus, + category, + division, + }); + + url.search = params.toString(); + const response = await fetch(url); + const data = await handleResponse(response); + + return { + clubs: data.clubs, + totalCount: data.totalCount, + }; + }, '클럽 데이터를 불러오는데 실패했습니다'); +}; + +export const updateClubDescription = async ( + updatedData: ClubDescription, +): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch(`${API_BASE_URL}/api/club/description`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updatedData), + }); + await handleResponse(response); + }, 'Failed to update club description'); +}; + +export const updateClubDetail = async ( + updatedData: Partial, +): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch(`${API_BASE_URL}/api/club/info`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updatedData), + }); + await handleResponse(response); + }, 'Failed to update club detail'); +}; diff --git a/frontend/src/apis/getClubDetail.ts b/frontend/src/apis/getClubDetail.ts deleted file mode 100644 index c65d9f8f6..000000000 --- a/frontend/src/apis/getClubDetail.ts +++ /dev/null @@ -1,17 +0,0 @@ -import API_BASE_URL from '@/constants/api'; -import { ClubDetail } from '@/types/club'; - -export const getClubDetail = async (clubId: string): Promise => { - try { - const response = await fetch(`${API_BASE_URL}/api/club/${clubId}`); - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.statusText}`); - } - - const result = await response.json(); - return result.data.club; - } catch (error) { - console.error('Error fetching club details', error); - throw error; - } -}; diff --git a/frontend/src/apis/getClubList.ts b/frontend/src/apis/getClubList.ts deleted file mode 100644 index 4af81d240..000000000 --- a/frontend/src/apis/getClubList.ts +++ /dev/null @@ -1,30 +0,0 @@ -import API_BASE_URL from '@/constants/api'; - -export const getClubList = async ( - keyword: string = '', - recruitmentStatus: string = 'all', - category: string = 'all', - division: string = 'all', -) => { - const url = new URL(`${API_BASE_URL}/api/club/search/`); - const params = new URLSearchParams({ - keyword, - recruitmentStatus, - category, - division, - }); - - url.search = params.toString(); - - const response = await fetch(url); - - if (!response.ok) { - throw new Error('클럽 데이터를 불러오는데 실패했습니다'); - } - - const result = await response.json(); - return { - clubs: result.data.clubs, - totalCount: result.data.totalCount, - }; -}; diff --git a/frontend/src/apis/image.ts b/frontend/src/apis/image.ts new file mode 100644 index 000000000..acd2e6bf6 --- /dev/null +++ b/frontend/src/apis/image.ts @@ -0,0 +1,156 @@ +import { secureFetch } from './auth/secureFetch'; +import API_BASE_URL from '@/constants/api'; +import { handleResponse, withErrorHandling } from './utils/apiHelpers'; + +interface PresignedData { + presignedUrl: string; + finalUrl: string; +} + +interface FeedUploadRequest { + fileName: string; + contentType: string; +} + +// Storage 업로드 +export async function uploadToStorage( + presignedUrl: string, + file: File, +): Promise { + return withErrorHandling(async () => { + const response = await fetch(presignedUrl, { + method: 'PUT', + body: file, + headers: { 'Content-Type': file.type }, + }); + await handleResponse(response, `S3 업로드 실패 : ${response.status}`); + }, 'S3 업로드 중 오류 발생'); +} + +// Cover API +export const coverApi = { + getUploadUrl: async ( + clubId: string, + fileName: string, + contentType: string, + ): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/cover/upload-url`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileName, contentType }), + }, + ); + return handleResponse(response, `커버 업로드 URL 생성 실패 : ${response.status}`); + }, '커버 업로드 URL 생성 중 오류 발생'); + }, + + completeUpload: async (clubId: string, fileUrl: string): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/cover/complete`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileUrl }), + }, + ); + await handleResponse(response, `커버 업로드 완료 처리 실패 : ${response.status}`); + }, '커버 업로드 완료 처리 중 오류 발생'); + }, + + delete: async (clubId: string): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/cover`, + { + method: 'DELETE', + }, + ); + await handleResponse(response, `커버 삭제 실패: ${response.status}`); + }, '커버 삭제 중 오류 발생'); + }, +}; + +// Feed API +export const feedApi = { + getUploadUrls: async ( + clubId: string, + uploadRequests: FeedUploadRequest[], + ): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/feed/upload-url`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(uploadRequests), + }, + ); + return handleResponse(response, `피드 업로드 URL 생성 실패 : ${response.status}`); + }, '피드 업로드 URL 생성 중 오류 발생'); + }, + + updateFeeds: async (clubId: string, feedUrls: string[]): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/feeds`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ feeds: feedUrls }), + }, + ); + await handleResponse(response, `피드 업데이트 실패 : ${response.status}`); + }, '피드 업데이트 중 오류 발생'); + }, +}; + +// Logo API +export const logoApi = { + getUploadUrl: async ( + clubId: string, + fileName: string, + contentType: string, + ): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/logo/upload-url`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileName, contentType }), + }, + ); + return handleResponse(response, `업로드 URL 생성 실패 : ${response.status}`); + }, '로고 업로드 URL 생성 중 오류 발생'); + }, + + completeUpload: async (clubId: string, fileUrl: string): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/logo/complete`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileUrl }), + }, + ); + await handleResponse(response, `업로드 완료 처리 실패 : ${response.status}`); + }, '로고 업로드 완료 처리 중 오류 발생'); + }, + + delete: async (clubId: string): Promise => { + return withErrorHandling(async () => { + const response = await secureFetch( + `${API_BASE_URL}/api/club/${clubId}/logo`, + { + method: 'DELETE', + }, + ); + await handleResponse(response, `로고 삭제 실패 : ${response.status}`); + }, '로고 삭제 중 오류 발생'); + }, +}; diff --git a/frontend/src/apis/image/cover.ts b/frontend/src/apis/image/cover.ts deleted file mode 100644 index 194b24e9f..000000000 --- a/frontend/src/apis/image/cover.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; - -interface PresignedData { - presignedUrl: string; - finalUrl: string; -} - -export const coverApi = { - getUploadUrl: async ( - clubId: string, - fileName: string, - contentType: string, - ): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/cover/upload-url`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileName, contentType }), - }, - ); - - if (!response.ok) { - throw new Error(`커버 업로드 URL 생성 실패 : ${response.status}`); - } - - const result = await response.json(); - return result.data; - }, - - completeUpload: async (clubId: string, fileUrl: string): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/cover/complete`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileUrl }), - }, - ); - - if (!response.ok) { - throw new Error(`커버 업로드 완료 처리 실패 : ${response.status}`); - } - }, - - delete: async (clubId: string): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/cover`, - { - method: 'DELETE', - }, - ); - - if (!response.ok) { - throw new Error(`커버 삭제 실패: ${response.status}`); - } - }, -}; diff --git a/frontend/src/apis/image/feed.ts b/frontend/src/apis/image/feed.ts deleted file mode 100644 index 4fa51f4c0..000000000 --- a/frontend/src/apis/image/feed.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; - -interface FeedUploadRequest { - fileName: string; - contentType: string; -} - -interface PresignedData { - presignedUrl: string; - finalUrl: string; -} - -export const feedApi = { - getUploadUrls: async ( - clubId: string, - uploadRequests: FeedUploadRequest[], - ): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/feed/upload-url`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(uploadRequests), - }, - ); - - if (!response.ok) { - throw new Error(`피드 업로드 URL 생성 실패 : ${response.status}`); - } - - const result = await response.json(); - return result.data; - }, - - updateFeeds: async (clubId: string, feedUrls: string[]): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/feeds`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ feeds: feedUrls }), - }, - ); - - if (!response.ok) { - throw new Error(`피드 업데이트 실패 : ${response.status}`); - } - }, -}; diff --git a/frontend/src/apis/image/logo.ts b/frontend/src/apis/image/logo.ts deleted file mode 100644 index f5dd9d776..000000000 --- a/frontend/src/apis/image/logo.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; - -interface PresignedData { - presignedUrl: string; - finalUrl: string; -} - -export const logoApi = { - getUploadUrl: async ( - clubId: string, - fileName: string, - contentType: string, - ): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/logo/upload-url`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileName, contentType }), - }, - ); - - if (!response.ok) { - throw new Error(`업로드 URL 생성 실패 : ${response.status}`); - } - - const result = await response.json(); - return result.data; - }, - - completeUpload: async (clubId: string, fileUrl: string): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/logo/complete`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ fileUrl }), - }, - ); - if (!response.ok) { - throw new Error(`업로드 완료 처리 실패 : ${response.status}`); - } - }, - - delete: async (clubId: string): Promise => { - const response = await secureFetch( - `${API_BASE_URL}/api/club/${clubId}/logo`, - { - method: 'DELETE', - }, - ); - - if (!response.ok) { - throw new Error(`로고 삭제 실패 : ${response.status}`); - } - }, -}; diff --git a/frontend/src/apis/image/uploadToStorage.ts b/frontend/src/apis/image/uploadToStorage.ts deleted file mode 100644 index 4d64091c6..000000000 --- a/frontend/src/apis/image/uploadToStorage.ts +++ /dev/null @@ -1,14 +0,0 @@ -export async function uploadToStorage( - presignedUrl: string, - file: File, -): Promise { - const response = await fetch(presignedUrl, { - method: 'PUT', - body: file, - headers: { 'Content-Type': file.type }, - }); - - if (!response.ok) { - throw new Error(`S3 업로드 실패 : ${response.status}`); - } -} diff --git a/frontend/src/apis/updateClubDescription.ts b/frontend/src/apis/updateClubDescription.ts deleted file mode 100644 index 893aad903..000000000 --- a/frontend/src/apis/updateClubDescription.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; -import { ClubDescription } from '@/types/club'; - -export const updateClubDescription = async ( - updatedData: ClubDescription, -): Promise => { - const response = await secureFetch(`${API_BASE_URL}/api/club/description`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(updatedData), - }); - - if (!response.ok) { - let errorMessage = `Failed to update club (HTTP ${response.status})`; - - try { - const errorResult = await response.json(); - if (errorResult?.message) { - errorMessage += `: ${errorResult.message}`; - } - } catch (error) { - console.error('📌 오류 응답 JSON 파싱 실패:', error); - } - - throw new Error(errorMessage); - } - - try { - await response.json(); - } catch (error) { - console.error('📌 JSON 파싱 실패:', error); - throw new Error('Invalid JSON response from API'); - } -}; diff --git a/frontend/src/apis/updateClubDetail.ts b/frontend/src/apis/updateClubDetail.ts deleted file mode 100644 index 896ed8fd1..000000000 --- a/frontend/src/apis/updateClubDetail.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { secureFetch } from '@/apis/auth/secureFetch'; -import API_BASE_URL from '@/constants/api'; -import { ClubDetail } from '@/types/club'; - -export const updateClubDetail = async ( - updatedData: Partial, -): Promise => { - const response = await secureFetch(`${API_BASE_URL}/api/club/info`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(updatedData), - }); - - let result; - try { - result = await response.json(); - } catch (error) { - console.error('📌 JSON 파싱 실패:', error); - result = null; - } - - if (!response.ok) { - const errorMessage = result?.message - ? `Failed to update club (HTTP ${response.status}): ${result.message}` - : `Failed to update club (HTTP ${response.status})`; - - throw new Error(errorMessage); - } - - if (!result?.data) { - console.error('📌 API 응답에 data 필드가 없음:', result); - throw new Error('Unexpected API response: Missing data field'); - } -}; diff --git a/frontend/src/apis/utils/apiHelpers.ts b/frontend/src/apis/utils/apiHelpers.ts new file mode 100644 index 000000000..73fb7d6b6 --- /dev/null +++ b/frontend/src/apis/utils/apiHelpers.ts @@ -0,0 +1,35 @@ +export const handleResponse = async ( + response: Response, + customErrorMessage?: string, +) => { + if (!response.ok) { + if (customErrorMessage) { + throw new Error(customErrorMessage); + } + + let message = response.statusText; + try { + const errorData = await response.json(); + if (errorData?.message) { + message = errorData.message; + } + } catch { + // JSON 파싱 실패시 statusText 사용 + } + throw new Error(message); + } + const result = await response.json(); + return result.data; +}; + +export const withErrorHandling = async ( + apiCall: () => Promise, + errorMessage: string, +): Promise => { + try { + return await apiCall(); + } catch (error) { + console.error(errorMessage, error); + throw error; + } +}; diff --git a/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts b/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts index 4b7de159d..a0d6acc36 100644 --- a/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts +++ b/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import deleteApplicants from '@/apis/applicants/deleteApplicants'; +import { deleteApplicants } from '@/apis/applicants'; export const useDeleteApplicants = (applicationFormId: string) => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/queries/applicants/useGetApplicants.ts b/frontend/src/hooks/queries/applicants/useGetApplicants.ts index 3811ec683..80fa59fc3 100644 --- a/frontend/src/hooks/queries/applicants/useGetApplicants.ts +++ b/frontend/src/hooks/queries/applicants/useGetApplicants.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import getClubApplicants from '@/apis/applicants/getClubApplicants'; +import { getClubApplicants } from '@/apis/applicants'; export const useGetApplicants = (applicationFormId: string | undefined) => { return useQuery({ diff --git a/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts b/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts index 58c2ed7cd..508128a51 100644 --- a/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts +++ b/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { updateApplicantDetail } from '@/apis/application/updateApplicantDetail'; +import { updateApplicantDetail } from '@/apis/application'; import { UpdateApplicantParams } from '@/types/applicants'; export const useUpdateApplicant = (applicationFormId: string | undefined) => { diff --git a/frontend/src/hooks/queries/application/useDeleteApplication.ts b/frontend/src/hooks/queries/application/useDeleteApplication.ts index 6d106b180..891afce45 100644 --- a/frontend/src/hooks/queries/application/useDeleteApplication.ts +++ b/frontend/src/hooks/queries/application/useDeleteApplication.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import deleteApplication from '@/apis/application/deleteApplication'; +import { deleteApplication } from '@/apis/application'; export const useDeleteApplication = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/queries/application/useDuplicateApplication.ts b/frontend/src/hooks/queries/application/useDuplicateApplication.ts index 756884205..af9286c57 100644 --- a/frontend/src/hooks/queries/application/useDuplicateApplication.ts +++ b/frontend/src/hooks/queries/application/useDuplicateApplication.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { duplicateApplication } from '@/apis/application/duplicateApplication'; +import { duplicateApplication } from '@/apis/application'; export const useDuplicateApplication = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/hooks/queries/application/useGetApplication.ts b/frontend/src/hooks/queries/application/useGetApplication.ts index 4914978da..6d8512b8e 100644 --- a/frontend/src/hooks/queries/application/useGetApplication.ts +++ b/frontend/src/hooks/queries/application/useGetApplication.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import getApplication from '@/apis/application/getApplication'; +import { getApplication } from '@/apis/application'; export const useGetApplication = ( clubId: string | undefined, diff --git a/frontend/src/hooks/queries/application/useGetApplicationlist.ts b/frontend/src/hooks/queries/application/useGetApplicationlist.ts index d2ec2ea7a..1ef74d447 100644 --- a/frontend/src/hooks/queries/application/useGetApplicationlist.ts +++ b/frontend/src/hooks/queries/application/useGetApplicationlist.ts @@ -1,10 +1,10 @@ import { useQuery } from '@tanstack/react-query'; -import getAllApplications from '@/apis/application/getAllApplications'; +import { getAllApplicationForms } from '@/apis/application'; export const useGetApplicationlist = () => { return useQuery({ queryKey: ['applicationForm'], - queryFn: () => getAllApplications(), + queryFn: () => getAllApplicationForms(), retry: false, }); }; diff --git a/frontend/src/hooks/queries/club/cover/useCoverMutation.ts b/frontend/src/hooks/queries/club/cover/useCoverMutation.ts index 5c1a80688..319cb8d9a 100644 --- a/frontend/src/hooks/queries/club/cover/useCoverMutation.ts +++ b/frontend/src/hooks/queries/club/cover/useCoverMutation.ts @@ -1,6 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { coverApi } from '@/apis/image/cover'; -import { uploadToStorage } from '@/apis/image/uploadToStorage'; +import { coverApi, uploadToStorage } from '@/apis/image'; interface CoverUploadParams { clubId: string; diff --git a/frontend/src/hooks/queries/club/images/useFeedMutation.ts b/frontend/src/hooks/queries/club/images/useFeedMutation.ts index 35db567e3..2c5d57762 100644 --- a/frontend/src/hooks/queries/club/images/useFeedMutation.ts +++ b/frontend/src/hooks/queries/club/images/useFeedMutation.ts @@ -1,6 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { feedApi } from '@/apis/image/feed'; -import { uploadToStorage } from '@/apis/image/uploadToStorage'; +import { feedApi, uploadToStorage } from '@/apis/image'; interface FeedUploadParams { clubId: string; diff --git a/frontend/src/hooks/queries/club/images/useLogoMutation.ts b/frontend/src/hooks/queries/club/images/useLogoMutation.ts index b9d82a353..0fac83dea 100644 --- a/frontend/src/hooks/queries/club/images/useLogoMutation.ts +++ b/frontend/src/hooks/queries/club/images/useLogoMutation.ts @@ -1,6 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { logoApi } from '@/apis/image/logo'; -import { uploadToStorage } from '@/apis/image/uploadToStorage'; +import { logoApi, uploadToStorage } from '@/apis/image'; interface LogoUploadParams { clubId: string; diff --git a/frontend/src/hooks/queries/club/useGetCardList.ts b/frontend/src/hooks/queries/club/useGetCardList.ts index 5dcce8d63..32e955855 100644 --- a/frontend/src/hooks/queries/club/useGetCardList.ts +++ b/frontend/src/hooks/queries/club/useGetCardList.ts @@ -1,5 +1,5 @@ import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import { getClubList } from '@/apis/getClubList'; +import { getClubList } from '@/apis/club'; import { ClubSearchResponse } from '@/types/club.responses'; import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; diff --git a/frontend/src/hooks/queries/club/useGetClubDetail.ts b/frontend/src/hooks/queries/club/useGetClubDetail.ts index dca982417..f911aca85 100644 --- a/frontend/src/hooks/queries/club/useGetClubDetail.ts +++ b/frontend/src/hooks/queries/club/useGetClubDetail.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { getClubDetail } from '@/apis/getClubDetail'; +import { getClubDetail } from '@/apis/club'; import { ClubDetail } from '@/types/club'; import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; diff --git a/frontend/src/hooks/queries/club/useUpdateClubDescription.ts b/frontend/src/hooks/queries/club/useUpdateClubDescription.ts index fcdedb1de..5be614af5 100644 --- a/frontend/src/hooks/queries/club/useUpdateClubDescription.ts +++ b/frontend/src/hooks/queries/club/useUpdateClubDescription.ts @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import { updateClubDescription } from '@/apis/updateClubDescription'; +import { updateClubDescription } from '@/apis/club'; import { ClubDescription } from '@/types/club'; export const useUpdateClubDescription = () => { diff --git a/frontend/src/hooks/queries/club/useUpdateClubDetail.ts b/frontend/src/hooks/queries/club/useUpdateClubDetail.ts index d33429671..8a20d59be 100644 --- a/frontend/src/hooks/queries/club/useUpdateClubDetail.ts +++ b/frontend/src/hooks/queries/club/useUpdateClubDetail.ts @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import { updateClubDetail } from '@/apis/updateClubDetail'; +import { updateClubDetail } from '@/apis/club'; import { ClubDetail } from '@/types/club'; export const useUpdateClubDetail = () => { diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 9e72244d9..25cc8efc2 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { getClubIdByToken } from '@/apis/auth/getClubIdByToken'; +import { getClubIdByToken } from '@/apis/auth'; const useAuth = () => { const [isLoading, setIsLoading] = useState(true); diff --git a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx index 95e76bf23..0776e7af0 100644 --- a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx +++ b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { login } from '@/apis/auth/login'; +import { login } from '@/apis/auth'; import moadong_name_logo from '@/assets/images/logos/moadong_name_logo.svg'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index 7d648f7d7..9d6367fbc 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { logout } from '@/apis/auth/logout'; +import { logout } from '@/apis/auth'; import { ADMIN_EVENT } from '@/constants/eventName'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './SideBar.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx index ae6c9b66f..62a4543f1 100644 --- a/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/AccountEditTab/AccountEditTab.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { changePassword } from '@/apis/auth/changePassword'; +import { changePassword } from '@/apis/auth'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index 69baa6cef..83d836092 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import styled from 'styled-components'; -import { updateApplicationStatus } from '@/apis/application/updateApplication'; +import { updateApplicationStatus } from '@/apis/application'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx index 5b913867b..d0400e399 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx @@ -1,8 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { createApplication } from '@/apis/application/createApplication'; -import { updateApplication } from '@/apis/application/updateApplication'; +import { createApplication, updateApplication } from '@/apis/application'; import Button from '@/components/common/Button/Button'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx index ead69d188..5cbfd5a03 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useQueryClient } from '@tanstack/react-query'; import styled from 'styled-components'; -import { updateApplicationStatus } from '@/apis/application/updateApplication'; +import { updateApplicationStatus } from '@/apis/application'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; diff --git a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx index 9efe0384e..5f3ab8279 100644 --- a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx +++ b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import applyToClub from '@/apis/application/applyToClub'; +import { applyToClub } from '@/apis/application'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx index 8be4a6df6..dc993d09b 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx @@ -1,12 +1,11 @@ import * as Styled from './ClubApplyButton.styles'; import { useNavigate, useParams } from 'react-router-dom'; import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; -import getApplication from '@/apis/application/getApplication'; +import { getApplication, getApplicationOptions } from '@/apis/application'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { USER_EVENT } from '@/constants/eventName'; import { useState } from 'react'; import { ApplicationForm, ApplicationFormMode } from '@/types/application'; -import getApplicationOptions from '@/apis/application/getApplicationOptions'; import ApplicationSelectModal from '@/components/application/modals/ApplicationSelectModal'; import ShareButton from '../ShareButton/ShareButton'; From ee5d022bbe7a7e9d8f7d57fcedbcb6507bdfc82a Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 18:31:26 +0900 Subject: [PATCH 03/19] =?UTF-8?q?refactor:=20API=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20?= =?UTF-8?q?React=20Query=20keys=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 함수에 사용자 친화적인 한국어 에러 메시지 추가 - queryKeys를 constants/queryKeys.ts로 분리 및 factory 패턴 적용 - hooks/queries → hooks/Queries 폴더 구조 정리 --- frontend/src/apis/applicants.ts | 4 +- frontend/src/apis/application.ts | 12 +-- frontend/src/apis/auth.ts | 8 +- frontend/src/apis/club.ts | 8 +- .../common/Header/admin/AdminProfile.tsx | 2 +- frontend/src/constants/queryKeys.ts | 22 +++++ frontend/src/hooks/Queries/useApplicants.ts | 56 +++++++++++++ frontend/src/hooks/Queries/useApplication.ts | 65 +++++++++++++++ frontend/src/hooks/Queries/useClub.ts | 82 +++++++++++++++++++ .../useClubCover.ts} | 9 +- .../useClubImages.ts} | 73 +++++++++++++++-- .../queries/applicants/useDeleteApplicants.ts | 19 ----- .../queries/applicants/useGetApplicants.ts | 11 --- .../queries/applicants/useUpdateApplicant.ts | 24 ------ .../application/useDeleteApplication.ts | 19 ----- .../application/useDuplicateApplication.ts | 19 ----- .../queries/application/useGetApplication.ts | 14 ---- .../application/useGetApplicationlist.ts | 11 --- .../queries/club/images/useLogoMutation.ts | 57 ------------- .../src/hooks/queries/club/useGetCardList.ts | 31 ------- .../hooks/queries/club/useGetClubDetail.ts | 20 ----- .../queries/club/useUpdateClubDescription.ts | 14 ---- .../hooks/queries/club/useUpdateClubDetail.ts | 14 ---- frontend/src/pages/AdminPage/AdminPage.tsx | 2 +- .../ClubCoverEditor/ClubCoverEditor.tsx | 2 +- .../ClubLogoEditor/ClubLogoEditor.tsx | 2 +- .../ApplicantDetailPage.tsx | 4 +- .../ApplicantsListTab/ApplicantsListTab.tsx | 6 +- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 8 +- .../ApplicationEditTab/ApplicationEditTab.tsx | 2 +- .../ApplicationListTab/ApplicationListTab.tsx | 8 +- .../tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | 2 +- .../ClubIntroEditTab/ClubIntroEditTab.tsx | 2 +- .../tabs/PhotoEditTab/PhotoEditTab.tsx | 2 +- .../tabs/RecruitEditTab/RecruitEditTab.tsx | 2 +- .../ApplicationFormPage.tsx | 4 +- .../pages/ClubDetailPage/ClubDetailPage.tsx | 2 +- .../ClubApplyButton/ClubApplyButton.tsx | 2 +- .../components/ShareButton/ShareButton.tsx | 2 +- frontend/src/pages/MainPage/MainPage.tsx | 2 +- 40 files changed, 343 insertions(+), 305 deletions(-) create mode 100644 frontend/src/constants/queryKeys.ts create mode 100644 frontend/src/hooks/Queries/useApplicants.ts create mode 100644 frontend/src/hooks/Queries/useApplication.ts create mode 100644 frontend/src/hooks/Queries/useClub.ts rename frontend/src/hooks/{queries/club/cover/useCoverMutation.ts => Queries/useClubCover.ts} (82%) rename frontend/src/hooks/{queries/club/images/useFeedMutation.ts => Queries/useClubImages.ts} (61%) delete mode 100644 frontend/src/hooks/queries/applicants/useDeleteApplicants.ts delete mode 100644 frontend/src/hooks/queries/applicants/useGetApplicants.ts delete mode 100644 frontend/src/hooks/queries/applicants/useUpdateApplicant.ts delete mode 100644 frontend/src/hooks/queries/application/useDeleteApplication.ts delete mode 100644 frontend/src/hooks/queries/application/useDuplicateApplication.ts delete mode 100644 frontend/src/hooks/queries/application/useGetApplication.ts delete mode 100644 frontend/src/hooks/queries/application/useGetApplicationlist.ts delete mode 100644 frontend/src/hooks/queries/club/images/useLogoMutation.ts delete mode 100644 frontend/src/hooks/queries/club/useGetCardList.ts delete mode 100644 frontend/src/hooks/queries/club/useGetClubDetail.ts delete mode 100644 frontend/src/hooks/queries/club/useUpdateClubDescription.ts delete mode 100644 frontend/src/hooks/queries/club/useUpdateClubDetail.ts diff --git a/frontend/src/apis/applicants.ts b/frontend/src/apis/applicants.ts index b51f08686..e2be4eca5 100644 --- a/frontend/src/apis/applicants.ts +++ b/frontend/src/apis/applicants.ts @@ -7,7 +7,7 @@ export const getClubApplicants = async (applicationFormId: string) => { const response = await secureFetch( `${API_BASE_URL}/api/club/apply/info/${applicationFormId}`, ); - return handleResponse(response); + return handleResponse(response, '지원자 목록을 불러오는데 실패했습니다.'); }, 'Error fetching club applicants'); }; @@ -26,6 +26,6 @@ export const deleteApplicants = async ( body: JSON.stringify({ applicantIds: applicantIds }), }, ); - return handleResponse(response); + return handleResponse(response, '지원자 삭제에 실패했습니다.'); }, 'Error fetching delete applicants'); }; diff --git a/frontend/src/apis/application.ts b/frontend/src/apis/application.ts index 714146fa2..b9ba1aad0 100644 --- a/frontend/src/apis/application.ts +++ b/frontend/src/apis/application.ts @@ -22,7 +22,7 @@ export const applyToClub = async ( }), }, ); - return handleResponse(response); + return handleResponse(response, '답변 제출에 실패했습니다.'); }, '답변 제출 중 오류 발생:'); }; @@ -35,7 +35,7 @@ export const createApplication = async (data: ApplicationFormData) => { }, body: JSON.stringify(data), }); - return handleResponse(response); + return handleResponse(response, '지원서 제출에 실패했습니다.'); }, '지원서 제출 중 오류 발생:'); }; @@ -59,7 +59,7 @@ export const duplicateApplication = async (applicationFormId: string) => { method: 'POST', }, ); - return handleResponse(response); + return handleResponse(response, '지원서 복제에 실패했습니다.'); }, '지원서 복제 중 오류 발생:'); }; @@ -123,7 +123,7 @@ export const updateApplicantDetail = async ( body: JSON.stringify(applicant), }, ); - return handleResponse(response); + return handleResponse(response, '지원자의 지원서 정보 수정에 실패했습니다.'); }, '지원자의 지원서 정보 수정 중 오류 발생:'); }; @@ -142,7 +142,7 @@ export const updateApplication = async ( body: JSON.stringify(data), }, ); - return handleResponse(response); + return handleResponse(response, '지원서 수정에 실패했습니다.'); }, '지원서 수정 중 오류 발생:'); }; @@ -163,6 +163,6 @@ export const updateApplicationStatus = async ( body: JSON.stringify({ active: newStatus }), }, ); - return handleResponse(response); + return handleResponse(response, '지원서 상태 수정에 실패했습니다.'); }, '지원서 상태 수정 중 오류 발생:'); }; diff --git a/frontend/src/apis/auth.ts b/frontend/src/apis/auth.ts index c7b6ace98..085706889 100644 --- a/frontend/src/apis/auth.ts +++ b/frontend/src/apis/auth.ts @@ -24,7 +24,7 @@ export const login = async ( }, body: JSON.stringify({ userId, password }), }); - return handleResponse(response); + return handleResponse(response, '로그인에 실패하였습니다.'); }, '로그인 중 오류 발생'); }; @@ -41,7 +41,7 @@ export const logout = async (): Promise => { credentials: 'include', }); - await handleResponse(response); + await handleResponse(response, '로그아웃에 실패하였습니다.'); }, '로그아웃 중 오류 발생'); }; @@ -53,7 +53,7 @@ export const getClubIdByToken = async (): Promise => { method: 'POST', }, ); - const data = await handleResponse(response); + const data = await handleResponse(response, '인증에 실패했습니다.'); return data.clubId; }, 'ClubId 조회 중 오류 발생'); }; @@ -69,6 +69,6 @@ export const changePassword = async ( }, body: JSON.stringify(payload), }); - await handleResponse(response); + await handleResponse(response, '비밀번호 변경에 실패했습니다.'); }, '비밀번호 변경 중 오류 발생'); }; diff --git a/frontend/src/apis/club.ts b/frontend/src/apis/club.ts index 3c0b18b0d..05c627b08 100644 --- a/frontend/src/apis/club.ts +++ b/frontend/src/apis/club.ts @@ -6,7 +6,7 @@ import { handleResponse, withErrorHandling } from './utils/apiHelpers'; export const getClubDetail = async (clubId: string): Promise => { return withErrorHandling(async () => { const response = await fetch(`${API_BASE_URL}/api/club/${clubId}`); - const data = await handleResponse(response); + const data = await handleResponse(response, '클럽 정보를 불러오는데 실패했습니다.'); return data.club; }, 'Error fetching club details'); }; @@ -28,7 +28,7 @@ export const getClubList = async ( url.search = params.toString(); const response = await fetch(url); - const data = await handleResponse(response); + const data = await handleResponse(response, '클럽 데이터를 불러오는데 실패했습니다.'); return { clubs: data.clubs, @@ -48,7 +48,7 @@ export const updateClubDescription = async ( }, body: JSON.stringify(updatedData), }); - await handleResponse(response); + await handleResponse(response, '클럽 설명 수정에 실패했습니다.'); }, 'Failed to update club description'); }; @@ -63,6 +63,6 @@ export const updateClubDetail = async ( }, body: JSON.stringify(updatedData), }); - await handleResponse(response); + await handleResponse(response, '클럽 정보 수정에 실패했습니다.'); }, 'Failed to update club detail'); }; diff --git a/frontend/src/components/common/Header/admin/AdminProfile.tsx b/frontend/src/components/common/Header/admin/AdminProfile.tsx index 26c044fb3..b6565be39 100644 --- a/frontend/src/components/common/Header/admin/AdminProfile.tsx +++ b/frontend/src/components/common/Header/admin/AdminProfile.tsx @@ -1,6 +1,6 @@ import DefaultMoadongLogo from '@/assets/images/logos/default_profile_image.svg'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import * as Styled from '../Header.styles'; const AdminProfile = () => { diff --git a/frontend/src/constants/queryKeys.ts b/frontend/src/constants/queryKeys.ts new file mode 100644 index 000000000..2d4721ac3 --- /dev/null +++ b/frontend/src/constants/queryKeys.ts @@ -0,0 +1,22 @@ +export const queryKeys = { + applicants: { + all: ['clubApplicants'] as const, + detail: (applicationFormId: string) => + ['clubApplicants', applicationFormId] as const, + }, + application: { + all: ['applicationForm'] as const, + detail: (clubId: string, applicationFormId: string) => + ['applicationForm', clubId, applicationFormId] as const, + }, + club: { + all: ['clubs'] as const, + detail: (clubId: string) => ['clubDetail', clubId] as const, + list: ( + keyword: string, + recruitmentStatus: string, + category: string, + division: string, + ) => ['clubs', keyword, recruitmentStatus, category, division] as const, + }, +} as const; diff --git a/frontend/src/hooks/Queries/useApplicants.ts b/frontend/src/hooks/Queries/useApplicants.ts new file mode 100644 index 000000000..00a8d2899 --- /dev/null +++ b/frontend/src/hooks/Queries/useApplicants.ts @@ -0,0 +1,56 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { deleteApplicants, getClubApplicants } from '@/apis/applicants'; +import { updateApplicantDetail } from '@/apis/application'; +import { queryKeys } from '@/constants/queryKeys'; +import { UpdateApplicantParams } from '@/types/applicants'; + +export const useGetApplicants = (applicationFormId: string | undefined) => { + return useQuery({ + queryKey: applicationFormId + ? queryKeys.applicants.detail(applicationFormId) + : queryKeys.applicants.all, + queryFn: () => getClubApplicants(applicationFormId!), + retry: false, + enabled: !!applicationFormId, + }); +}; + +export const useDeleteApplicants = (applicationFormId: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ applicantIds }: { applicantIds: string[] }) => + deleteApplicants(applicantIds, applicationFormId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.applicants.detail(applicationFormId), + }); + }, + onError: (error) => { + console.error(`Error delete applicants detail: ${error}`); + }, + }); +}; + +export const useUpdateApplicant = (applicationFormId: string | undefined) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (applicant: UpdateApplicantParams[]) => { + if (!applicationFormId) { + throw new Error('Application Form ID가 유효하지 않습니다.'); + } + return updateApplicantDetail(applicant, applicationFormId); + }, + onSuccess: () => { + if (applicationFormId) { + queryClient.invalidateQueries({ + queryKey: queryKeys.applicants.detail(applicationFormId), + }); + } + }, + onError: (error) => { + console.log(`Error updating applicant detail: ${error}`); + }, + }); +}; diff --git a/frontend/src/hooks/Queries/useApplication.ts b/frontend/src/hooks/Queries/useApplication.ts new file mode 100644 index 000000000..93802095f --- /dev/null +++ b/frontend/src/hooks/Queries/useApplication.ts @@ -0,0 +1,65 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + deleteApplication, + duplicateApplication, + getApplication, + getAllApplicationForms, +} from '@/apis/application'; +import { queryKeys } from '@/constants/queryKeys'; + +export const useGetApplication = ( + clubId: string | undefined, + applicationFormId: string | undefined, +) => { + return useQuery({ + queryKey: + clubId && applicationFormId + ? queryKeys.application.detail(clubId, applicationFormId) + : queryKeys.application.all, + queryFn: () => getApplication(clubId!, applicationFormId!), + retry: false, + enabled: !!clubId && !!applicationFormId, + }); +}; + +export const useGetApplicationlist = () => { + return useQuery({ + queryKey: queryKeys.application.all, + queryFn: () => getAllApplicationForms(), + retry: false, + }); +}; + +export const useDeleteApplication = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (applicationFormId: string) => + deleteApplication(applicationFormId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.application.all, + }); + }, + onError: (error) => { + console.error(`Error delete application detail: ${error}`); + }, + }); +}; + +export const useDuplicateApplication = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (applicationFormId: string) => + duplicateApplication(applicationFormId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.application.all, + }); + }, + onError: (error) => { + console.error(`Error duplicating application: ${error}`); + }, + }); +}; diff --git a/frontend/src/hooks/Queries/useClub.ts b/frontend/src/hooks/Queries/useClub.ts new file mode 100644 index 000000000..4a90a6abe --- /dev/null +++ b/frontend/src/hooks/Queries/useClub.ts @@ -0,0 +1,82 @@ +import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'; +import { + getClubDetail, + getClubList, + updateClubDescription, + updateClubDetail, +} from '@/apis/club'; +import { queryKeys } from '@/constants/queryKeys'; +import { ClubDetail, ClubDescription } from '@/types/club'; +import { ClubSearchResponse } from '@/types/club.responses'; +import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; +import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; + +interface UseGetCardListProps { + keyword: string; + recruitmentStatus: string; + category: string; + division: string; +} + +export const useGetClubDetail = (clubId: string) => { + return useQuery({ + queryKey: queryKeys.club.detail(clubId), + queryFn: () => getClubDetail(clubId as string), + enabled: !!clubId, + select: (data) => + ({ + ...data, + logo: data.logo ? convertGoogleDriveUrl(data.logo) : undefined, + feeds: Array.isArray(data.feeds) + ? data.feeds.map(convertGoogleDriveUrl) + : [], + }) as ClubDetail, + }); +}; + +export const useGetCardList = ({ + keyword, + recruitmentStatus, + category, + division, +}: UseGetCardListProps) => { + return useQuery({ + queryKey: queryKeys.club.list( + keyword, + recruitmentStatus, + category, + division, + ), + queryFn: () => getClubList(keyword, recruitmentStatus, category, division), + placeholderData: keepPreviousData, + select: (data) => ({ + totalCount: data.totalCount, + clubs: data.clubs.map((club) => ({ + ...club, + logo: convertToDriveUrl(club.logo), + })), + }), + }); +}; + +export const useUpdateClubDescription = () => { + return useMutation({ + mutationFn: (updatedData: ClubDescription) => + updateClubDescription(updatedData), + + onError: (error) => { + console.error('Error updating club detail:', error); + }, + }); +}; + +export const useUpdateClubDetail = () => { + return useMutation({ + mutationFn: (updatedData: Partial) => + updateClubDetail(updatedData), + + onError: (error) => { + console.error('Error updating club detail:', error); + }, + }); +}; diff --git a/frontend/src/hooks/queries/club/cover/useCoverMutation.ts b/frontend/src/hooks/Queries/useClubCover.ts similarity index 82% rename from frontend/src/hooks/queries/club/cover/useCoverMutation.ts rename to frontend/src/hooks/Queries/useClubCover.ts index 319cb8d9a..bb07654bc 100644 --- a/frontend/src/hooks/queries/club/cover/useCoverMutation.ts +++ b/frontend/src/hooks/Queries/useClubCover.ts @@ -1,5 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { coverApi, uploadToStorage } from '@/apis/image'; +import { queryKeys } from '@/constants/queryKeys'; interface CoverUploadParams { clubId: string; @@ -25,7 +26,9 @@ export const useUploadCover = () => { }, onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', data.clubId] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(data.clubId), + }); }, onError: () => { @@ -44,7 +47,9 @@ export const useDeleteCover = () => { }, onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', data.clubId] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(data.clubId), + }); }, onError: () => { diff --git a/frontend/src/hooks/queries/club/images/useFeedMutation.ts b/frontend/src/hooks/Queries/useClubImages.ts similarity index 61% rename from frontend/src/hooks/queries/club/images/useFeedMutation.ts rename to frontend/src/hooks/Queries/useClubImages.ts index 2c5d57762..bc24b010f 100644 --- a/frontend/src/hooks/queries/club/images/useFeedMutation.ts +++ b/frontend/src/hooks/Queries/useClubImages.ts @@ -1,5 +1,6 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { feedApi, uploadToStorage } from '@/apis/image'; +import { feedApi, logoApi, uploadToStorage } from '@/apis/image'; +import { queryKeys } from '@/constants/queryKeys'; interface FeedUploadParams { clubId: string; @@ -12,7 +13,11 @@ interface FeedUpdateParams { urls: string[]; } -// 피드 업로드(새 파일 업로드 + 기존 피드와 합쳐서 서버 갱신) +interface LogoUploadParams { + clubId: string; + file: File; +} + export const useUploadFeed = () => { const queryClient = useQueryClient(); @@ -60,7 +65,9 @@ export const useUploadFeed = () => { }, onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', data.clubId] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(data.clubId), + }); // 부분 실패한 경우 사용자에게 알림 if (data.failedFiles.length > 0) { @@ -70,30 +77,78 @@ export const useUploadFeed = () => { ); } }, - // TODO: 각 API 에러 응답에 따른 세분화된 에러 메시지 전달 - // 참고: feedApi.updateFeeds, uploadToStorage 에러 스펙 확인 후 분기 onError: () => { alert('이미지 업로드에 실패했어요. 다시 시도해주세요!'); }, }); }; -// 피드 업데이트 (기존 피드 URL 배열만 서버에 갱신) export const useUpdateFeed = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ clubId, urls }: FeedUpdateParams) => { - // 1. 서버에 URL 배열 PUT으로 갱신 await feedApi.updateFeeds(clubId, urls); return { clubId }; }, onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', data.clubId] }); + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(data.clubId), + }); }, - // TODO: 각 API 에러 응답에 따른 세분화된 에러 메시지 전달 onError: () => { alert('이미지 수정에 실패했어요. 다시 시도해주세요!'); }, }); }; + +// Logo Hooks +export const useUploadLogo = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ clubId, file }: LogoUploadParams) => { + // 1. presigned URL 받기 + const { presignedUrl, finalUrl } = await logoApi.getUploadUrl( + clubId, + file.name, + file.type, + ); + + // 2. r2 업로드 + await uploadToStorage(presignedUrl, file); + + // 3. 완료 처리 + await logoApi.completeUpload(clubId, finalUrl); + + return { finalUrl, clubId }; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(data.clubId), + }); + }, + onError: () => { + alert('로고 업로드에 실패했어요. 다시 시도해주세요!'); + }, + }); +}; + +export const useDeleteLogo = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async (clubId: string) => { + await logoApi.delete(clubId); + return clubId; + }, + onSuccess: (clubId) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(clubId), + }); + }, + onError: () => { + alert('로고 초기화에 실패했어요. 다시 시도해 주세요.'); + }, + }); +}; diff --git a/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts b/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts deleted file mode 100644 index a0d6acc36..000000000 --- a/frontend/src/hooks/queries/applicants/useDeleteApplicants.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { deleteApplicants } from '@/apis/applicants'; - -export const useDeleteApplicants = (applicationFormId: string) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ applicantIds }: { applicantIds: string[] }) => - deleteApplicants(applicantIds, applicationFormId), - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['clubApplicants', applicationFormId], - }); - }, - onError: (error) => { - console.error(`Error delete applicants detail: ${error}`); - }, - }); -}; diff --git a/frontend/src/hooks/queries/applicants/useGetApplicants.ts b/frontend/src/hooks/queries/applicants/useGetApplicants.ts deleted file mode 100644 index 80fa59fc3..000000000 --- a/frontend/src/hooks/queries/applicants/useGetApplicants.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getClubApplicants } from '@/apis/applicants'; - -export const useGetApplicants = (applicationFormId: string | undefined) => { - return useQuery({ - queryKey: ['clubApplicants', applicationFormId], - queryFn: () => getClubApplicants(applicationFormId!), - retry: false, - enabled: !!applicationFormId, - }); -}; diff --git a/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts b/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts deleted file mode 100644 index 508128a51..000000000 --- a/frontend/src/hooks/queries/applicants/useUpdateApplicant.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { updateApplicantDetail } from '@/apis/application'; -import { UpdateApplicantParams } from '@/types/applicants'; - -export const useUpdateApplicant = (applicationFormId: string | undefined) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (applicant: UpdateApplicantParams[]) => { - if (!applicationFormId) { - throw new Error('Application Form ID가 유효하지 않습니다.'); - } - return updateApplicantDetail(applicant, applicationFormId); - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['clubApplicants', applicationFormId], - }); - }, - onError: (error) => { - console.log(`Error updating applicant detail: ${error}`); - }, - }); -}; diff --git a/frontend/src/hooks/queries/application/useDeleteApplication.ts b/frontend/src/hooks/queries/application/useDeleteApplication.ts deleted file mode 100644 index 891afce45..000000000 --- a/frontend/src/hooks/queries/application/useDeleteApplication.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { deleteApplication } from '@/apis/application'; - -export const useDeleteApplication = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (applicationFormId: string) => - deleteApplication(applicationFormId), - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['applicationForm'], - }); - }, - onError: (error) => { - console.error(`Error delete application detail: ${error}`); - }, - }); -}; diff --git a/frontend/src/hooks/queries/application/useDuplicateApplication.ts b/frontend/src/hooks/queries/application/useDuplicateApplication.ts deleted file mode 100644 index af9286c57..000000000 --- a/frontend/src/hooks/queries/application/useDuplicateApplication.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { duplicateApplication } from '@/apis/application'; - -export const useDuplicateApplication = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (applicationFormId: string) => - duplicateApplication(applicationFormId), - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['applicationForm'], - }); - }, - onError: (error) => { - console.error(`Error duplicating application: ${error}`); - }, - }); -}; diff --git a/frontend/src/hooks/queries/application/useGetApplication.ts b/frontend/src/hooks/queries/application/useGetApplication.ts deleted file mode 100644 index 6d8512b8e..000000000 --- a/frontend/src/hooks/queries/application/useGetApplication.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getApplication } from '@/apis/application'; - -export const useGetApplication = ( - clubId: string | undefined, - applicationFormId: string | undefined, -) => { - return useQuery({ - queryKey: ['applicationForm', clubId, applicationFormId], - queryFn: () => getApplication(clubId!, applicationFormId!), - retry: false, - enabled: !!clubId && !!applicationFormId, - }); -}; diff --git a/frontend/src/hooks/queries/application/useGetApplicationlist.ts b/frontend/src/hooks/queries/application/useGetApplicationlist.ts deleted file mode 100644 index 1ef74d447..000000000 --- a/frontend/src/hooks/queries/application/useGetApplicationlist.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getAllApplicationForms } from '@/apis/application'; - -export const useGetApplicationlist = () => { - return useQuery({ - queryKey: ['applicationForm'], - queryFn: () => getAllApplicationForms(), - retry: false, - }); -}; -export default useGetApplicationlist; diff --git a/frontend/src/hooks/queries/club/images/useLogoMutation.ts b/frontend/src/hooks/queries/club/images/useLogoMutation.ts deleted file mode 100644 index 0fac83dea..000000000 --- a/frontend/src/hooks/queries/club/images/useLogoMutation.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { logoApi, uploadToStorage } from '@/apis/image'; - -interface LogoUploadParams { - clubId: string; - file: File; -} - -// 로고 업로드 (presigned URL 발급 → r2 업로드 → 완료 처리) -export const useUploadLogo = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ clubId, file }: LogoUploadParams) => { - // 1. presigned URL 받기 - const { presignedUrl, finalUrl } = await logoApi.getUploadUrl( - clubId, - file.name, - file.type, - ); - - // 2. r2 업로드 - await uploadToStorage(presignedUrl, file); - - // 3. 완료 처리 - await logoApi.completeUpload(clubId, finalUrl); - - return { finalUrl, clubId }; - }, - onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', data.clubId] }); - }, - // TODO: 각 API 에러 응답에 따른 세분화된 에러 메시지 전달 - onError: () => { - alert('로고 업로드에 실패했어요. 다시 시도해주세요!'); - }, - }); -}; - -// 로고 삭제 -export const useDeleteLogo = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async (clubId: string) => { - await logoApi.delete(clubId); - return clubId; - }, - onSuccess: (clubId) => { - queryClient.invalidateQueries({ queryKey: ['clubDetail', clubId] }); - }, - // TODO: 각 API 에러 응답에 따른 세분화된 에러 메시지 전달 - onError: () => { - alert('로고 초기화에 실패했어요. 다시 시도해 주세요.'); - }, - }); -}; diff --git a/frontend/src/hooks/queries/club/useGetCardList.ts b/frontend/src/hooks/queries/club/useGetCardList.ts deleted file mode 100644 index 32e955855..000000000 --- a/frontend/src/hooks/queries/club/useGetCardList.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import { getClubList } from '@/apis/club'; -import { ClubSearchResponse } from '@/types/club.responses'; -import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; - -interface UseGetCardListProps { - keyword: string; - recruitmentStatus: string; - category: string; - division: string; -} - -export const useGetCardList = ({ - keyword, - recruitmentStatus, - category, - division, -}: UseGetCardListProps) => { - return useQuery({ - queryKey: ['clubs', keyword, recruitmentStatus, category, division], - queryFn: () => getClubList(keyword, recruitmentStatus, category, division), - placeholderData: keepPreviousData, - select: (data) => ({ - totalCount: data.totalCount, - clubs: data.clubs.map((club) => ({ - ...club, - logo: convertToDriveUrl(club.logo), - })), - }), - }); -}; diff --git a/frontend/src/hooks/queries/club/useGetClubDetail.ts b/frontend/src/hooks/queries/club/useGetClubDetail.ts deleted file mode 100644 index f911aca85..000000000 --- a/frontend/src/hooks/queries/club/useGetClubDetail.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getClubDetail } from '@/apis/club'; -import { ClubDetail } from '@/types/club'; -import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; - -export const useGetClubDetail = (clubId: string) => { - return useQuery({ - queryKey: ['clubDetail', clubId], - queryFn: () => getClubDetail(clubId as string), - enabled: !!clubId, - select: (data) => - ({ - ...data, - logo: data.logo ? convertGoogleDriveUrl(data.logo) : undefined, - feeds: Array.isArray(data.feeds) - ? data.feeds.map(convertGoogleDriveUrl) - : [], - }) as ClubDetail, - }); -}; diff --git a/frontend/src/hooks/queries/club/useUpdateClubDescription.ts b/frontend/src/hooks/queries/club/useUpdateClubDescription.ts deleted file mode 100644 index 5be614af5..000000000 --- a/frontend/src/hooks/queries/club/useUpdateClubDescription.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { updateClubDescription } from '@/apis/club'; -import { ClubDescription } from '@/types/club'; - -export const useUpdateClubDescription = () => { - return useMutation({ - mutationFn: (updatedData: ClubDescription) => - updateClubDescription(updatedData), - - onError: (error) => { - console.error('Error updating club detail:', error); - }, - }); -}; diff --git a/frontend/src/hooks/queries/club/useUpdateClubDetail.ts b/frontend/src/hooks/queries/club/useUpdateClubDetail.ts deleted file mode 100644 index 8a20d59be..000000000 --- a/frontend/src/hooks/queries/club/useUpdateClubDetail.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { updateClubDetail } from '@/apis/club'; -import { ClubDetail } from '@/types/club'; - -export const useUpdateClubDetail = () => { - return useMutation({ - mutationFn: (updatedData: Partial) => - updateClubDetail(updatedData), - - onError: (error) => { - console.error('Error updating club detail:', error); - }, - }); -}; diff --git a/frontend/src/pages/AdminPage/AdminPage.tsx b/frontend/src/pages/AdminPage/AdminPage.tsx index 6c6c0e44c..7a10ab00d 100644 --- a/frontend/src/pages/AdminPage/AdminPage.tsx +++ b/frontend/src/pages/AdminPage/AdminPage.tsx @@ -1,7 +1,7 @@ import { Outlet } from 'react-router-dom'; import Header from '@/components/common/Header/Header'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import SideBar from '@/pages/AdminPage/components/SideBar/SideBar'; import * as Styled from './AdminPage.styles'; diff --git a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx index 5a803c38d..0fee2c408 100644 --- a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx @@ -6,7 +6,7 @@ import { useAdminClubContext } from '@/context/AdminClubContext'; import { useDeleteCover, useUploadCover, -} from '@/hooks/Queries/club/cover/useCoverMutation'; +} from '@/hooks/Queries/useClubCover'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './ClubCoverEditor.styles'; diff --git a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx index 845c90c5c..5e19d52d6 100644 --- a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx @@ -6,7 +6,7 @@ import { useAdminClubContext } from '@/context/AdminClubContext'; import { useDeleteLogo, useUploadLogo, -} from '@/hooks/Queries/club/images/useLogoMutation'; +} from '@/hooks/Queries/useClubImages'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import * as Styled from './ClubLogoEditor.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx index f594a396f..a274ae271 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx @@ -6,8 +6,8 @@ import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { AVAILABLE_STATUSES } from '@/constants/status'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useUpdateApplicant } from '@/hooks/Queries/applicants/useUpdateApplicant'; -import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; +import { useUpdateApplicant } from '@/hooks/Queries/useApplicants'; +import { useGetApplication } from '@/hooks/Queries/useApplication'; import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; import { ApplicationStatus } from '@/types/applicants'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index 83d836092..d029f48df 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -6,8 +6,10 @@ import { updateApplicationStatus } from '@/apis/application'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; -import { useDeleteApplication } from '@/hooks/Queries/application/useDeleteApplication'; -import { useGetApplicationlist } from '@/hooks/Queries/application/useGetApplicationlist'; +import { + useDeleteApplication, + useGetApplicationlist, +} from '@/hooks/Queries/useApplication'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import * as Styled from '@/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index e6c20725a..b48b0c1d7 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -7,9 +7,11 @@ import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDow import SearchField from '@/components/common/SearchField/SearchField'; import { AVAILABLE_STATUSES } from '@/constants/status'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useDeleteApplicants } from '@/hooks/Queries/applicants/useDeleteApplicants'; -import { useGetApplicants } from '@/hooks/Queries/applicants/useGetApplicants'; -import { useUpdateApplicant } from '@/hooks/Queries/applicants/useUpdateApplicant'; +import { + useDeleteApplicants, + useGetApplicants, + useUpdateApplicant, +} from '@/hooks/Queries/useApplicants'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { Applicant, ApplicationStatus } from '@/types/applicants'; import mapStatusToGroup from '@/utils/mapStatusToGroup'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx index d0400e399..601d42e9b 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx @@ -7,7 +7,7 @@ import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; import INITIAL_FORM_DATA from '@/constants/INITIAL_FORM_DATA'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; +import { useGetApplication } from '@/hooks/Queries/useApplication'; import QuestionBuilder from '@/pages/AdminPage/components/QuestionBuilder/QuestionBuilder'; import { PageContainer } from '@/styles/PageContainer.styles'; import { diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx index 5cbfd5a03..9860fb773 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx @@ -6,9 +6,11 @@ import { updateApplicationStatus } from '@/apis/application'; import expandArrow from '@/assets/images/icons/ExpandArrow.svg'; import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; -import { useDeleteApplication } from '@/hooks/Queries/application/useDeleteApplication'; -import { useDuplicateApplication } from '@/hooks/Queries/application/useDuplicateApplication'; -import { useGetApplicationlist } from '@/hooks/Queries/application/useGetApplicationlist'; +import { + useDeleteApplication, + useDuplicateApplication, + useGetApplicationlist, +} from '@/hooks/Queries/useApplication'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { ApplicationFormItem, SemesterGroup } from '@/types/application'; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx index 84f1eaf82..d835c08ad 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx @@ -5,7 +5,7 @@ import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import { SNS_CONFIG } from '@/constants/snsConfig'; -import { useUpdateClubDetail } from '@/hooks/Queries/club/useUpdateClubDetail'; +import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import ClubCoverEditor from '@/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor'; diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx index e57d1ed19..bd6f782a4 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx @@ -4,7 +4,7 @@ import { useQueryClient } from '@tanstack/react-query'; import Button from '@/components/common/Button/Button'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDetail } from '@/hooks/Queries/club/useUpdateClubDetail'; +import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index e4b8db386..86883e4a7 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -6,7 +6,7 @@ import { MAX_FILE_COUNT, MAX_FILE_SIZE } from '@/constants/uploadLimit'; import { useUpdateFeed, useUploadFeed, -} from '@/hooks/Queries/club/images/useFeedMutation'; +} from '@/hooks/Queries/useClubImages'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx index 955e8de74..6f0f1ac1b 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx @@ -5,7 +5,7 @@ import { setYear } from 'date-fns'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDescription } from '@/hooks/Queries/club/useUpdateClubDescription'; +import { useUpdateClubDescription } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; diff --git a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx index 5f3ab8279..d0336073f 100644 --- a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx +++ b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx @@ -4,8 +4,8 @@ import { applyToClub } from '@/apis/application'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetApplication } from '@/hooks/Queries/application/useGetApplication'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetApplication } from '@/hooks/Queries/useApplication'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import { useAnswers } from '@/hooks/Application/useAnswers'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; diff --git a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx index d4db00b34..d21996a2a 100644 --- a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx +++ b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx @@ -3,7 +3,7 @@ import { useParams, useSearchParams } from 'react-router-dom'; import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import useDevice from '@/hooks/useDevice'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx index dc993d09b..47dfbbb5a 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx @@ -1,6 +1,6 @@ import * as Styled from './ClubApplyButton.styles'; import { useNavigate, useParams } from 'react-router-dom'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import { getApplication, getApplicationOptions } from '@/apis/application'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import { USER_EVENT } from '@/constants/eventName'; diff --git a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx index c05542256..1eee406d5 100644 --- a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx @@ -1,7 +1,7 @@ import ShareIcon from '@/assets/images/icons/share_icon.svg'; import ShareIconMobile from '@/assets/images/icons/share_icon_mobile.svg'; import { USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/Queries/club/useGetClubDetail'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useDevice from '@/hooks/useDevice'; import * as Styled from './ShareButton.styles'; diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index 20fe5bc4b..aa1023278 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -3,7 +3,7 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW } from '@/constants/eventName'; -import { useGetCardList } from '@/hooks/Queries/club/useGetCardList'; +import { useGetCardList } from '@/hooks/Queries/useClub'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import Banner from '@/pages/MainPage/components/Banner/Banner'; import CategoryButtonList from '@/pages/MainPage/components/CategoryButtonList/CategoryButtonList'; From 960041749daf5ddd69d07acd29be7e71efb8f423 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 22:19:07 +0900 Subject: [PATCH 04/19] =?UTF-8?q?refactor:=20=EC=95=88=20=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/InfoTabs/useAutoScroll.ts | 48 -------------- .../src/hooks/PhotoList/usePhotoNavigation.ts | 64 ------------------- .../hooks/PhotoList/useResponsiveLayout.ts | 38 ----------- .../hooks/PhotoModal/useModalNavigation.ts | 17 ----- 4 files changed, 167 deletions(-) delete mode 100644 frontend/src/hooks/InfoTabs/useAutoScroll.ts delete mode 100644 frontend/src/hooks/PhotoList/usePhotoNavigation.ts delete mode 100644 frontend/src/hooks/PhotoList/useResponsiveLayout.ts delete mode 100644 frontend/src/hooks/PhotoModal/useModalNavigation.ts diff --git a/frontend/src/hooks/InfoTabs/useAutoScroll.ts b/frontend/src/hooks/InfoTabs/useAutoScroll.ts deleted file mode 100644 index 6b860d93a..000000000 --- a/frontend/src/hooks/InfoTabs/useAutoScroll.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useCallback, useRef } from 'react'; - -/** - * 섹션 자동 스크롤을 위한 커스텀 훅 - * @param sectionCount - 섹션의 총 개수 - * @param yOffset - 스크롤 시 상단 여백 (기본값: -110) - * @returns {Object} sectionRefs와 scrollToSection 함수를 포함한 객체 - */ -const useAutoScroll = (sectionCount: number = 4, yOffset: number = -110) => { - const sectionRefs = useRef<(HTMLDivElement | null)[]>( - new Array(sectionCount).fill(null), - ); - - /** - * 지정된 인덱스의 섹션으로 스크롤 - * @param index - 스크롤할 섹션의 인덱스 - * @returns {boolean} 스크롤 성공 여부 - */ - const scrollToSection = useCallback( - (index: number): boolean => { - if (index < 0 || index >= sectionCount) { - return false; - } - - const element = sectionRefs.current[index]; - - if (!element) { - return false; - } - - try { - window.scrollTo({ - top: element.getBoundingClientRect().top + window.scrollY + yOffset, - behavior: 'smooth', - }); - return true; - } catch (error) { - console.error('Error scrolling to section:', error); - return false; - } - }, - [sectionCount, yOffset], - ); - - return { sectionRefs, scrollToSection }; -}; - -export default useAutoScroll; diff --git a/frontend/src/hooks/PhotoList/usePhotoNavigation.ts b/frontend/src/hooks/PhotoList/usePhotoNavigation.ts deleted file mode 100644 index 32b995ed8..000000000 --- a/frontend/src/hooks/PhotoList/usePhotoNavigation.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useCallback, useEffect } from 'react'; -import { USER_EVENT } from '@/constants/eventName'; -import useMixpanelTrack from '../Mixpanel/useMixpanelTrack'; - -export const usePhotoNavigation = ({ - currentIndex, - setCurrentIndex, - photosLength, - cardWidth, - containerWidth, - setTranslateX, - isMobile, -}: { - currentIndex: number; - setCurrentIndex: React.Dispatch>; - photosLength: number; - cardWidth: number; - containerWidth: number; - translateX: number; - setTranslateX: React.Dispatch>; - isMobile: boolean; -}) => { - const trackEvent = useMixpanelTrack(); - - const calculateTranslateX = useCallback( - (index: number) => -index * cardWidth, - [cardWidth], - ); - - useEffect(() => { - setTranslateX(calculateTranslateX(currentIndex)); - }, [currentIndex, containerWidth, cardWidth, photosLength]); - - const handleNext = () => { - const nextIndex = currentIndex + 1; - if (nextIndex >= photosLength) return; - setCurrentIndex(nextIndex); - trackEvent(USER_EVENT.PHOTO_NAVIGATION_CLICKED, { - action: 'next', - index: nextIndex, - }); - }; - - const handlePrev = () => { - if (currentIndex <= 0) return; - setCurrentIndex(currentIndex - 1); - trackEvent(USER_EVENT.PHOTO_NAVIGATION_CLICKED, { - action: 'prev', - index: currentIndex - 1, - }); - }; - - const canScrollLeft = currentIndex > 0 && photosLength > 1; - const canScrollRight = isMobile - ? currentIndex < photosLength - 1 - : currentIndex < photosLength - 2; - - return { - handlePrev, - handleNext, - canScrollLeft, - canScrollRight, - }; -}; diff --git a/frontend/src/hooks/PhotoList/useResponsiveLayout.ts b/frontend/src/hooks/PhotoList/useResponsiveLayout.ts deleted file mode 100644 index c82bcc24d..000000000 --- a/frontend/src/hooks/PhotoList/useResponsiveLayout.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; -import { DESKTOP_CARD_WIDTH, MOBILE_CARD_WIDTH } from '@/constants/photoLayout'; -import debounce from '@/utils/debounce'; - -export const useResponsiveLayout = ( - ref: React.RefObject, - breakPoint = 500, -) => { - const [isMobile, setIsMobile] = useState( - () => window.innerWidth <= breakPoint, - ); - const [containerWidth, setContainerWidth] = useState(0); - - useEffect(() => { - const updateIsMobile = () => setIsMobile(window.innerWidth <= breakPoint); - const updateContainerWidth = () => { - if (ref.current) { - setContainerWidth(ref.current.offsetWidth); - } - }; - - const handleResize = debounce(() => { - updateIsMobile(); - updateContainerWidth(); - }, 200); - - handleResize(); - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, [ref, breakPoint]); - - const cardWidth = useMemo( - () => (isMobile ? MOBILE_CARD_WIDTH : DESKTOP_CARD_WIDTH), - [isMobile], - ); - - return { isMobile, containerWidth, cardWidth }; -}; diff --git a/frontend/src/hooks/PhotoModal/useModalNavigation.ts b/frontend/src/hooks/PhotoModal/useModalNavigation.ts deleted file mode 100644 index 041e02391..000000000 --- a/frontend/src/hooks/PhotoModal/useModalNavigation.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback } from 'react'; - -export default function useModalNavigation( - currentIndex: number, - total: number, - setIndex: (index: number) => void, -) { - const handlePrev = useCallback(() => { - setIndex(currentIndex === 0 ? total - 1 : currentIndex - 1); - }, [currentIndex, total, setIndex]); - - const handleNext = useCallback(() => { - setIndex(currentIndex === total - 1 ? 0 : currentIndex + 1); - }, [currentIndex, total, setIndex]); - - return { handlePrev, handleNext }; -} From aab1f7010a7f58768b8c272fc57544780f5589b6 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 22:20:39 +0900 Subject: [PATCH 05/19] =?UTF-8?q?refactor:=20API=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=95=88=EC=A0=95=EC=84=B1=20=EA=B0=95?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apiHelpers/handleResponse: 빈 응답, JSON 파싱 실패, 잘못된 Content-Type에 대한 예외 처리 강화 - auth/getClubIdByToken: 응답 데이터 내 clubId 유효성 검사 로직 추가 - club/getClubDetail: 응답 데이터 내 club 객체 유효성 검사 로직 추가 - club/getClubList: 리스트 데이터 Null 체크 및 기본값(Fallback) 처리 추가 --- frontend/src/apis/auth.ts | 3 +++ frontend/src/apis/club.ts | 11 +++++++++-- frontend/src/apis/utils/apiHelpers.ts | 25 +++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/frontend/src/apis/auth.ts b/frontend/src/apis/auth.ts index 085706889..d66c4d3e7 100644 --- a/frontend/src/apis/auth.ts +++ b/frontend/src/apis/auth.ts @@ -54,6 +54,9 @@ export const getClubIdByToken = async (): Promise => { }, ); const data = await handleResponse(response, '인증에 실패했습니다.'); + if (!data?.clubId) { + throw new Error('ClubId를 가져올 수 없습니다.'); + } return data.clubId; }, 'ClubId 조회 중 오류 발생'); }; diff --git a/frontend/src/apis/club.ts b/frontend/src/apis/club.ts index 05c627b08..d805f7e4b 100644 --- a/frontend/src/apis/club.ts +++ b/frontend/src/apis/club.ts @@ -7,6 +7,9 @@ export const getClubDetail = async (clubId: string): Promise => { return withErrorHandling(async () => { const response = await fetch(`${API_BASE_URL}/api/club/${clubId}`); const data = await handleResponse(response, '클럽 정보를 불러오는데 실패했습니다.'); + if (!data?.club) { + throw new Error('클럽 정보를 가져올 수 없습니다.'); + } return data.club; }, 'Error fetching club details'); }; @@ -30,9 +33,13 @@ export const getClubList = async ( const response = await fetch(url); const data = await handleResponse(response, '클럽 데이터를 불러오는데 실패했습니다.'); + if (!data) { + throw new Error('클럽 데이터를 가져올 수 없습니다.'); + } + return { - clubs: data.clubs, - totalCount: data.totalCount, + clubs: data.clubs || [], + totalCount: data.totalCount || 0, }; }, '클럽 데이터를 불러오는데 실패했습니다'); }; diff --git a/frontend/src/apis/utils/apiHelpers.ts b/frontend/src/apis/utils/apiHelpers.ts index 73fb7d6b6..1c0b5715a 100644 --- a/frontend/src/apis/utils/apiHelpers.ts +++ b/frontend/src/apis/utils/apiHelpers.ts @@ -18,8 +18,29 @@ export const handleResponse = async ( } throw new Error(message); } - const result = await response.json(); - return result.data; + + // 응답이 비어있거나 JSON이 아닌 경우 처리 + const contentType = response.headers.get('content-type'); + const contentLength = response.headers.get('content-length'); + + // Content-Length가 0이거나 Content-Type이 JSON이 아니면 undefined 반환 + if (contentLength === '0' || !contentType?.includes('application/json')) { + return undefined; + } + + // 응답 본문이 있는지 확인 + const text = await response.text(); + if (!text) { + return undefined; + } + + try { + const result = JSON.parse(text); + return result.data; + } catch { + // JSON 파싱 실패 시 undefined 반환 + return undefined; + } }; export const withErrorHandling = async ( From 7cbc5463e545115b6146f32c24806704dee8005b Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 22:28:11 +0900 Subject: [PATCH 06/19] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/utils/apiHelpers.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/src/apis/utils/apiHelpers.ts b/frontend/src/apis/utils/apiHelpers.ts index 1c0b5715a..b7e9f2980 100644 --- a/frontend/src/apis/utils/apiHelpers.ts +++ b/frontend/src/apis/utils/apiHelpers.ts @@ -19,16 +19,12 @@ export const handleResponse = async ( throw new Error(message); } - // 응답이 비어있거나 JSON이 아닌 경우 처리 const contentType = response.headers.get('content-type'); const contentLength = response.headers.get('content-length'); - - // Content-Length가 0이거나 Content-Type이 JSON이 아니면 undefined 반환 + if (contentLength === '0' || !contentType?.includes('application/json')) { return undefined; } - - // 응답 본문이 있는지 확인 const text = await response.text(); if (!text) { return undefined; @@ -38,7 +34,6 @@ export const handleResponse = async ( const result = JSON.parse(text); return result.data; } catch { - // JSON 파싱 실패 시 undefined 반환 return undefined; } }; From bb81cd207aae2f0b0971a2cf09eebe4dd00b02c1 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 23:40:23 +0900 Subject: [PATCH 07/19] =?UTF-8?q?refactor:=20React=20Query=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=A0=84=EC=97=AD=ED=99=94=20=EB=B0=8F=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=A7=81=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?UI=20=EC=9C=84=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.tsx: QueryClient 전역 설정 추가 (staleTime: 60s, retry: 1) - Hooks: - 개별 훅 내 중복된 retry, staleTime 옵션 제거 - invalidateQueries 로직을 컴포넌트에서 훅 내부 onSuccess로 이동하여 응집도 향상 - 훅 내부 alert 제거 및 console.error로 로깅 전환 (SoC 준수) - Components: - mutate 호출 시 onError/onSuccess 콜백을 통해 사용자 알림(alert) 처리하도록 수정 --- frontend/src/App.tsx | 12 ++++- frontend/src/hooks/Queries/useApplicants.ts | 3 +- frontend/src/hooks/Queries/useApplication.ts | 4 +- frontend/src/hooks/Queries/useClub.ts | 28 +++++++++-- frontend/src/hooks/Queries/useClubCover.ts | 4 +- frontend/src/hooks/Queries/useClubImages.ts | 17 ++----- .../ClubCoverEditor/ClubCoverEditor.tsx | 20 +++++--- .../ClubLogoEditor/ClubLogoEditor.tsx | 20 +++++--- .../tabs/PhotoEditTab/PhotoEditTab.tsx | 47 +++++++++++++------ .../tabs/RecruitEditTab/RecruitEditTab.tsx | 5 +- 10 files changed, 105 insertions(+), 55 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9823c2b02..9feac9a3d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,7 +16,17 @@ import ClubUnionPage from './pages/ClubUnionPage/ClubUnionPage'; import IntroducePage from './pages/IntroducePage/IntroducePage'; import 'swiper/css'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + retry: 1, + }, + mutations: { + retry: 0, + }, + }, +}); const AdminRoutes = lazy(() => import('@/pages/AdminPage/AdminRoutes')); diff --git a/frontend/src/hooks/Queries/useApplicants.ts b/frontend/src/hooks/Queries/useApplicants.ts index 00a8d2899..37318ed87 100644 --- a/frontend/src/hooks/Queries/useApplicants.ts +++ b/frontend/src/hooks/Queries/useApplicants.ts @@ -10,7 +10,6 @@ export const useGetApplicants = (applicationFormId: string | undefined) => { ? queryKeys.applicants.detail(applicationFormId) : queryKeys.applicants.all, queryFn: () => getClubApplicants(applicationFormId!), - retry: false, enabled: !!applicationFormId, }); }; @@ -50,7 +49,7 @@ export const useUpdateApplicant = (applicationFormId: string | undefined) => { } }, onError: (error) => { - console.log(`Error updating applicant detail: ${error}`); + console.error(`Error updating applicant detail: ${error}`); }, }); }; diff --git a/frontend/src/hooks/Queries/useApplication.ts b/frontend/src/hooks/Queries/useApplication.ts index 93802095f..aa595be78 100644 --- a/frontend/src/hooks/Queries/useApplication.ts +++ b/frontend/src/hooks/Queries/useApplication.ts @@ -2,8 +2,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { deleteApplication, duplicateApplication, - getApplication, getAllApplicationForms, + getApplication, } from '@/apis/application'; import { queryKeys } from '@/constants/queryKeys'; @@ -17,7 +17,6 @@ export const useGetApplication = ( ? queryKeys.application.detail(clubId, applicationFormId) : queryKeys.application.all, queryFn: () => getApplication(clubId!, applicationFormId!), - retry: false, enabled: !!clubId && !!applicationFormId, }); }; @@ -26,7 +25,6 @@ export const useGetApplicationlist = () => { return useQuery({ queryKey: queryKeys.application.all, queryFn: () => getAllApplicationForms(), - retry: false, }); }; diff --git a/frontend/src/hooks/Queries/useClub.ts b/frontend/src/hooks/Queries/useClub.ts index 4a90a6abe..d19449f67 100644 --- a/frontend/src/hooks/Queries/useClub.ts +++ b/frontend/src/hooks/Queries/useClub.ts @@ -1,4 +1,9 @@ -import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'; +import { + keepPreviousData, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; import { getClubDetail, getClubList, @@ -6,7 +11,7 @@ import { updateClubDetail, } from '@/apis/club'; import { queryKeys } from '@/constants/queryKeys'; -import { ClubDetail, ClubDescription } from '@/types/club'; +import { ClubDescription, ClubDetail } from '@/types/club'; import { ClubSearchResponse } from '@/types/club.responses'; import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; @@ -22,6 +27,7 @@ export const useGetClubDetail = (clubId: string) => { return useQuery({ queryKey: queryKeys.club.detail(clubId), queryFn: () => getClubDetail(clubId as string), + staleTime: 60 * 1000, enabled: !!clubId, select: (data) => ({ @@ -48,6 +54,7 @@ export const useGetCardList = ({ division, ), queryFn: () => getClubList(keyword, recruitmentStatus, category, division), + staleTime: 60 * 1000, placeholderData: keepPreviousData, select: (data) => ({ totalCount: data.totalCount, @@ -60,10 +67,16 @@ export const useGetCardList = ({ }; export const useUpdateClubDescription = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: (updatedData: ClubDescription) => updateClubDescription(updatedData), - + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(variables.id), + }); + }, onError: (error) => { console.error('Error updating club detail:', error); }, @@ -71,9 +84,18 @@ export const useUpdateClubDescription = () => { }; export const useUpdateClubDetail = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: (updatedData: Partial) => updateClubDetail(updatedData), + onSuccess: (_, variables) => { + if (variables.id) { + queryClient.invalidateQueries({ + queryKey: queryKeys.club.detail(variables.id), + }); + } + }, onError: (error) => { console.error('Error updating club detail:', error); diff --git a/frontend/src/hooks/Queries/useClubCover.ts b/frontend/src/hooks/Queries/useClubCover.ts index bb07654bc..58b3c6480 100644 --- a/frontend/src/hooks/Queries/useClubCover.ts +++ b/frontend/src/hooks/Queries/useClubCover.ts @@ -32,7 +32,7 @@ export const useUploadCover = () => { }, onError: () => { - alert('커버 이미지 업로드에 실패했어요. 다시 시도해주세요!'); + console.error('Error uploading cover'); }, }); }; @@ -53,7 +53,7 @@ export const useDeleteCover = () => { }, onError: () => { - alert('커버 이미지 삭제에 실패했어요. 다시 시도해주세요!'); + console.error('Error deleting cover'); }, }); }; diff --git a/frontend/src/hooks/Queries/useClubImages.ts b/frontend/src/hooks/Queries/useClubImages.ts index bc24b010f..cdec8bdd7 100644 --- a/frontend/src/hooks/Queries/useClubImages.ts +++ b/frontend/src/hooks/Queries/useClubImages.ts @@ -68,17 +68,9 @@ export const useUploadFeed = () => { queryClient.invalidateQueries({ queryKey: queryKeys.club.detail(data.clubId), }); - - // 부분 실패한 경우 사용자에게 알림 - if (data.failedFiles.length > 0) { - const failedFileNames = data.failedFiles.join(', '); - alert( - `일부 파일 업로드에 실패했어요.\n실패한 파일: ${failedFileNames}\n\n성공한 파일은 정상적으로 등록되었어요.`, - ); - } }, onError: () => { - alert('이미지 업로드에 실패했어요. 다시 시도해주세요!'); + console.error('Error uploading feed images'); }, }); }; @@ -97,12 +89,11 @@ export const useUpdateFeed = () => { }); }, onError: () => { - alert('이미지 수정에 실패했어요. 다시 시도해주세요!'); + console.error('Error updating feed images'); }, }); }; -// Logo Hooks export const useUploadLogo = () => { const queryClient = useQueryClient(); @@ -129,7 +120,7 @@ export const useUploadLogo = () => { }); }, onError: () => { - alert('로고 업로드에 실패했어요. 다시 시도해주세요!'); + console.error('Error uploading logo'); }, }); }; @@ -148,7 +139,7 @@ export const useDeleteLogo = () => { }); }, onError: () => { - alert('로고 초기화에 실패했어요. 다시 시도해 주세요.'); + console.error('Error deleting logo'); }, }); }; diff --git a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx index 0fee2c408..b1f69a190 100644 --- a/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor.tsx @@ -3,11 +3,8 @@ import defaultCover from '@/assets/images/logos/default_profile_image.svg'; import { ADMIN_EVENT } from '@/constants/eventName'; import { MAX_FILE_SIZE } from '@/constants/uploadLimit'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { - useDeleteCover, - useUploadCover, -} from '@/hooks/Queries/useClubCover'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import { useDeleteCover, useUploadCover } from '@/hooks/Queries/useClubCover'; import * as Styled from './ClubCoverEditor.styles'; interface ClubCoverEditorProps { @@ -42,7 +39,14 @@ const ClubCoverEditor = ({ coverImage }: ClubCoverEditorProps) => { } trackEvent(ADMIN_EVENT.CLUB_COVER_UPLOAD_BUTTON_CLICKED); - uploadMutation.mutate({ clubId, file }); + uploadMutation.mutate( + { clubId, file }, + { + onError: () => { + alert('커버 이미지 업로드에 실패했어요. 다시 시도해주세요!'); + }, + }, + ); }; const triggerFileInput = () => { @@ -59,7 +63,11 @@ const ClubCoverEditor = ({ coverImage }: ClubCoverEditorProps) => { if (!window.confirm('정말 커버 이미지를 기본 이미지로 되돌릴까요?')) return; trackEvent(ADMIN_EVENT.CLUB_COVER_RESET_BUTTON_CLICKED); - deleteMutation.mutate(clubId); + deleteMutation.mutate(clubId, { + onError: () => { + alert('커버 이미지 초기화에 실패했어요. 다시 시도해주세요!'); + }, + }); }; return ( diff --git a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx index 5e19d52d6..ab0252c30 100644 --- a/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx +++ b/frontend/src/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor.tsx @@ -3,11 +3,8 @@ import defaultLogo from '@/assets/images/logos/default_profile_image.svg'; import { ADMIN_EVENT } from '@/constants/eventName'; import { MAX_FILE_SIZE } from '@/constants/uploadLimit'; import { useAdminClubContext } from '@/context/AdminClubContext'; -import { - useDeleteLogo, - useUploadLogo, -} from '@/hooks/Queries/useClubImages'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import { useDeleteLogo, useUploadLogo } from '@/hooks/Queries/useClubImages'; import * as Styled from './ClubLogoEditor.styles'; interface ClubLogoEditorProps { @@ -41,7 +38,14 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { } trackEvent(ADMIN_EVENT.CLUB_LOGO_UPLOAD_BUTTON_CLICKED); - uploadMutation.mutate({ clubId, file }); + uploadMutation.mutate( + { clubId, file }, + { + onError: () => { + alert('로고 업로드에 실패했어요. 다시 시도해주세요!'); + }, + }, + ); }; const triggerFileInput = () => { @@ -58,7 +62,11 @@ const ClubLogoEditor = ({ clubLogo }: ClubLogoEditorProps) => { if (!window.confirm('정말 로고를 기본 이미지로 되돌릴까요?')) return; trackEvent(ADMIN_EVENT.CLUB_LOGO_RESET_BUTTON_CLICKED); - deleteMutation.mutate(clubId); + deleteMutation.mutate(clubId, { + onError: () => { + alert('로고 초기화에 실패했어요. 다시 시도해 주세요.'); + }, + }); }; return ( diff --git a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx index 86883e4a7..222d33278 100644 --- a/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx @@ -3,12 +3,9 @@ import { useOutletContext } from 'react-router-dom'; import Button from '@/components/common/Button/Button'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import { MAX_FILE_COUNT, MAX_FILE_SIZE } from '@/constants/uploadLimit'; -import { - useUpdateFeed, - useUploadFeed, -} from '@/hooks/Queries/useClubImages'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useUpdateFeed, useUploadFeed } from '@/hooks/Queries/useClubImages'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { ImagePreview } from '@/pages/AdminPage/tabs/PhotoEditTab/components/ImagePreview/ImagePreview'; import { ClubDetail } from '@/types/club'; @@ -36,11 +33,26 @@ const PhotoEditTab = () => { const handleFiles = (files: FileList | null) => { if (!files || files.length === 0) return; - uploadFeed({ - clubId: clubDetail.id, - files: Array.from(files), - existingUrls: imageList, - }); + uploadFeed( + { + clubId: clubDetail.id, + files: Array.from(files), + existingUrls: imageList, + }, + { + onSuccess: (data) => { + if (data.failedFiles.length > 0) { + const failedFileNames = data.failedFiles.join(', '); + alert( + `일부 파일 업로드에 실패했어요.\n실패한 파일: ${failedFileNames}\n\n성공한 파일은 정상적으로 등록되었어요.`, + ); + } + }, + onError: () => { + alert('이미지 업로드에 실패했어요. 다시 시도해주세요!'); + }, + }, + ); }; const handleUploadClick = () => { @@ -56,7 +68,6 @@ const PhotoEditTab = () => { inputRef.current?.click(); }; - /** 파일 선택 변경 */ const handleFileChange = (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; @@ -76,17 +87,23 @@ const PhotoEditTab = () => { handleFiles(files); }; - /** 이미지 삭제 */ const deleteImage = (index: number) => { if (isLoading) return; const newList = imageList.filter((_, i) => i !== index); setImageList(newList); - updateFeed({ - clubId: clubDetail.id, - urls: newList, - }); + updateFeed( + { + clubId: clubDetail.id, + urls: newList, + }, + { + onError: () => { + alert('이미지 삭제에 실패했어요. 다시 시도해주세요!'); + }, + }, + ); }; return ( diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx index 6f0f1ac1b..f4395ca0d 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx @@ -5,9 +5,9 @@ import { setYear } from 'date-fns'; import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDescription } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useUpdateClubDescription } from '@/hooks/Queries/useClub'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import Calendar from '@/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar'; import { ClubDetail } from '@/types/club'; @@ -122,9 +122,6 @@ const RecruitEditTab = () => { updateClubDescription(updatedData, { onSuccess: () => { alert('모집 정보가 성공적으로 수정되었습니다.'); - queryClient.invalidateQueries({ - queryKey: ['clubDetail', clubDetail.id], - }); }, onError: (error) => { alert(`모집 정보 수정에 실패했습니다: ${error.message}`); From b92aa0859a23f38ad42d1baf241ab04b3401b06b Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sat, 17 Jan 2026 23:53:58 +0900 Subject: [PATCH 08/19] =?UTF-8?q?refactor:=20=EC=A7=80=EC=9B=90=EC=84=9C?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20=EB=B0=8F=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20=ED=9B=85=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hooks: useUpdateApplicationStatus 훅 추가 및 useDuplicateApplication 적용 - ApplicationListTab: 상태 변경 로직을 훅으로 위임하고 누락된 onDuplicate 핸들러 연결 - ApplicantDetailPage: 지원자 정보 수정 시 에러 핸들링(onError) 추가 - ApplicantsTab: 코드 정리 및 라우팅 관련 수정 --- frontend/src/hooks/Queries/useApplication.ts | 23 +++++++ .../ApplicantDetailPage.tsx | 18 ++++-- .../ApplicantsListTab/ApplicantsListTab.tsx | 64 ++++++++++++------- .../tabs/ApplicantsTab/ApplicantsTab.tsx | 15 ++--- 4 files changed, 84 insertions(+), 36 deletions(-) diff --git a/frontend/src/hooks/Queries/useApplication.ts b/frontend/src/hooks/Queries/useApplication.ts index aa595be78..53607fec4 100644 --- a/frontend/src/hooks/Queries/useApplication.ts +++ b/frontend/src/hooks/Queries/useApplication.ts @@ -4,6 +4,7 @@ import { duplicateApplication, getAllApplicationForms, getApplication, + updateApplicationStatus, } from '@/apis/application'; import { queryKeys } from '@/constants/queryKeys'; @@ -61,3 +62,25 @@ export const useDuplicateApplication = () => { }, }); }; + +export const useUpdateApplicationStatus = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + applicationFormId, + currentStatus, + }: { + applicationFormId: string; + currentStatus: string; + }) => updateApplicationStatus(applicationFormId, currentStatus), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.application.all, + }); + }, + onError: (error) => { + console.error('Error updating application status:', error); + }, + }); +}; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx index a274ae271..6342f2011 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx @@ -77,13 +77,20 @@ const ApplicantDetailPage = () => { if (typeof memo !== 'string') return; if (!isApplicationStatus(status)) return; - updateApplicant([ + updateApplicant( + [ + { + memo, + status, + applicantId: questionId, + }, + ], { - memo, - status, - applicantId: questionId, + onError: () => { + alert('지원자 정보 수정에 실패했습니다.'); + }, }, - ]); + ); }, 400), [clubId, questionId, updateApplicant], ); @@ -98,7 +105,6 @@ const ApplicantDetailPage = () => { if (isLoading) return ; if (isError || !formData) return
지원서 정보를 불러올 수 없습니다.
; - // questionId로 지원자 찾기 if (!applicant) { return
해당 지원자를 찾을 수 없습니다.
; } diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index d029f48df..c65ff8bae 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -8,20 +8,26 @@ import Plus from '@/assets/images/icons/Plus.svg'; import Spinner from '@/components/common/Spinner/Spinner'; import { useDeleteApplication, + useDuplicateApplication, useGetApplicationlist, + useUpdateApplicationStatus, } from '@/hooks/Queries/useApplication'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import * as Styled from '@/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.styles'; import { ApplicationFormItem, SemesterGroup } from '@/types/application'; +const MAX_INITIAL_ITEMS = 3; + const ApplicationListTab = () => { const { data: allforms, isLoading, isError, error } = useGetApplicationlist(); - const queryClient = useQueryClient(); - const navigate = useNavigate(); const { mutate: deleteApplication } = useDeleteApplication(); + const { mutate: duplicateApplication } = useDuplicateApplication(); + const { mutate: updateStatus } = useUpdateApplicationStatus(); + + const navigate = useNavigate(); + const [isExpanded, setIsExpanded] = useState(false); - const MAX_INITIAL_ITEMS = 3; const handleGoToNewForm = () => { navigate('/admin/application-list/edit'); @@ -31,7 +37,6 @@ const ApplicationListTab = () => { }; const handleDeleteApplication = (applicationFormId: string) => { - // 사용자에게 재확인 if ( window.confirm( '지원서 양식을 정말 삭제하시겠습니까?\n삭제된 양식은 복구할 수 없습니다.', @@ -40,8 +45,22 @@ const ApplicationListTab = () => { deleteApplication(applicationFormId, { onSuccess: () => { setOpenMenuId(null); - // 성공 알림 - alert('삭제되었습니다.'); + }, + onError: () => { + alert('삭제에 실패했습니다.'); + }, + }); + } + }; + + const handleDuplicateApplication = (applicationFormId: string) => { + if (window.confirm('이 지원서 양식을 복제하시겠습니까?')) { + duplicateApplication(applicationFormId, { + onSuccess: () => { + setOpenMenuId(null); + }, + onError: () => { + alert('지원서 복제에 실패했습니다.'); }, }); } @@ -51,14 +70,17 @@ const ApplicationListTab = () => { applicationFormId: string, currentStatus: string, ) => { - try { - await updateApplicationStatus(applicationFormId, currentStatus); - queryClient.invalidateQueries({ queryKey: ['applicationForm'] }); - setOpenMenuId(null); - } catch (error) { - console.error('지원서 상태 변경 실패:', error); - alert('상태 변경에 실패했습니다.'); - } + updateStatus( + { applicationFormId, currentStatus }, + { + onSuccess: () => { + setOpenMenuId(null); + }, + onError: () => { + alert('상태 변경에 실패했습니다.'); + }, + }, + ); }; const handleToggleExpand = () => { @@ -73,30 +95,26 @@ const ApplicationListTab = () => { id: string, contextPrefix: string, ) => { - e.stopPropagation(); // 이벤트 버블링 방지 (row 전체가 클릭되지 않도록) + e.stopPropagation(); const uniqueKey = `${contextPrefix}-${id}`; - setOpenMenuId(openMenuId === uniqueKey ? null : uniqueKey); // 같은 버튼 누르면 닫기, 다른 버튼 누르면 열기 + setOpenMenuId(openMenuId === uniqueKey ? null : uniqueKey); }; useEffect(() => { - //더보기 메뉴 외부 클릭 시 메뉴 닫기 const handleOutsideClick = (e: MouseEvent) => { - // menuRef.current가 있고, 클릭된 영역이 메뉴 영역(menuRef.current)에 포함되지 않을 때 if (menuRef.current && !menuRef.current.contains(e.target as Node)) { - setOpenMenuId(null); // 메뉴를 닫습니다. + setOpenMenuId(null); } }; - // 메뉴가 열려 있을 때만 이벤트 리스너를 추가합니다. if (openMenuId !== null) { document.addEventListener('mousedown', handleOutsideClick); } - // 클린업 함수: 컴포넌트가 사라지거나, openMenuId가 바뀌기 전에 리스너를 제거합니다. return () => { document.removeEventListener('mousedown', handleOutsideClick); }; - }, [openMenuId]); // openMenuId가 변경될 때마다 이 훅을 다시 실행합니다. + }, [openMenuId]); if (isLoading) { return ; @@ -152,6 +170,7 @@ const ApplicationListTab = () => { onEdit={handleGoToDetailForm} onMenuToggle={handleMenuToggle} onDelete={handleDeleteApplication} + onDuplicate={handleDuplicateApplication} /> ))} {showExpandButton && ( @@ -208,6 +227,7 @@ const ApplicationListTab = () => { onMenuToggle={handleMenuToggle} onToggleStatus={handleToggleClick} onDelete={handleDeleteApplication} + onDuplicate={handleDuplicateApplication} /> ))} diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx index b48b0c1d7..626bf5625 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx @@ -17,8 +17,15 @@ import { Applicant, ApplicationStatus } from '@/types/applicants'; import mapStatusToGroup from '@/utils/mapStatusToGroup'; import * as Styled from './ApplicantsTab.styles'; +const sortOptions = [ + { value: 'date', label: '제출순' }, + { value: 'name', label: '이름순' }, +] as const; + const ApplicantsTab = () => { + const { clubId, applicantsData, setApplicantsData } = useAdminClubContext(); const { applicationFormId } = useParams<{ applicationFormId: string }>(); + const navigate = useNavigate(); const statusOptions = AVAILABLE_STATUSES.map((status) => ({ value: status, @@ -35,13 +42,6 @@ const ApplicantsTab = () => { }), ); - const sortOptions = [ - { value: 'date', label: '제출순' }, - { value: 'name', label: '이름순' }, - ] as const; - - const navigate = useNavigate(); - const { clubId, applicantsData, setApplicantsData } = useAdminClubContext(); const { data: fetchData, isLoading, @@ -68,7 +68,6 @@ const ApplicantsTab = () => { } }, [fetchData, setApplicantsData]); - // 모든 드롭다운을 닫는 함수 const closeAllDropdowns = () => { if (open) setOpen(false); if (isStatusDropdownOpen) setIsStatusDropdownOpen(false); From c4a2c2dde9117827342219f0571d9e321fea5da7 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:25:25 +0900 Subject: [PATCH 09/19] =?UTF-8?q?refactor:=20constants=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=EC=9D=84=20camelCase=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - APPLICATION_FORM.ts → applicationForm.ts - CLUB_UNION_INFO.ts → clubUnionInfo.ts - INITIAL_FORM_DATA.ts → initialFormData.ts - 관련 import 경로 9개 파일 업데이트 --- .../application/QuestionDescription/QuestionDescription.tsx | 2 +- .../components/application/QuestionTitle/QuestionTitle.tsx | 2 +- frontend/src/components/application/questionTypes/Choice.tsx | 2 +- .../src/components/application/questionTypes/LongText.tsx | 2 +- .../src/components/application/questionTypes/ShortText.tsx | 2 +- .../src/constants/{APPLICATION_FORM.ts => applicationForm.ts} | 0 .../src/constants/{CLUB_UNION_INFO.ts => clubUnionInfo.ts} | 0 .../constants/{INITIAL_FORM_DATA.ts => initialFormData.ts} | 0 .../AdminPage/components/QuestionBuilder/QuestionBuilder.tsx | 2 +- .../AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx | 4 ++-- frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx | 2 +- frontend/src/types/application.ts | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename frontend/src/constants/{APPLICATION_FORM.ts => applicationForm.ts} (100%) rename frontend/src/constants/{CLUB_UNION_INFO.ts => clubUnionInfo.ts} (100%) rename frontend/src/constants/{INITIAL_FORM_DATA.ts => initialFormData.ts} (100%) diff --git a/frontend/src/components/application/QuestionDescription/QuestionDescription.tsx b/frontend/src/components/application/QuestionDescription/QuestionDescription.tsx index 826b7c24d..10b706c01 100644 --- a/frontend/src/components/application/QuestionDescription/QuestionDescription.tsx +++ b/frontend/src/components/application/QuestionDescription/QuestionDescription.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; import styled from 'styled-components'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; interface QuestionDescriptionProps { description: string; diff --git a/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx b/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx index 3bb16ed99..e8c0f2f99 100644 --- a/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx +++ b/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx @@ -1,5 +1,5 @@ import { useLayoutEffect, useRef } from 'react'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; import useDevice from '@/hooks/useDevice'; import * as Styled from './QuestionTitle.styles'; diff --git a/frontend/src/components/application/questionTypes/Choice.tsx b/frontend/src/components/application/questionTypes/Choice.tsx index c27e4b6bd..7f4245846 100644 --- a/frontend/src/components/application/questionTypes/Choice.tsx +++ b/frontend/src/components/application/questionTypes/Choice.tsx @@ -2,7 +2,7 @@ import DeleteIcon from '@/assets/images/icons/delete_choice.svg'; import QuestionDescription from '@/components/application/QuestionDescription/QuestionDescription'; import QuestionTitle from '@/components/application/QuestionTitle/QuestionTitle'; import InputField from '@/components/common/InputField/InputField'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; import ChoiceItem from '@/pages/ApplicationFormPage/components/ChoiceItem/ChoiceItem'; import { ChoiceProps } from '@/types/application'; import * as Styled from './Choice.styles'; diff --git a/frontend/src/components/application/questionTypes/LongText.tsx b/frontend/src/components/application/questionTypes/LongText.tsx index c687d649d..2f7f07e79 100644 --- a/frontend/src/components/application/questionTypes/LongText.tsx +++ b/frontend/src/components/application/questionTypes/LongText.tsx @@ -1,7 +1,7 @@ import QuestionDescription from '@/components/application/QuestionDescription/QuestionDescription'; import QuestionTitle from '@/components/application/QuestionTitle/QuestionTitle'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; import { TextProps } from '@/types/application'; const LongText = ({ diff --git a/frontend/src/components/application/questionTypes/ShortText.tsx b/frontend/src/components/application/questionTypes/ShortText.tsx index c2c6e8077..abd5219ad 100644 --- a/frontend/src/components/application/questionTypes/ShortText.tsx +++ b/frontend/src/components/application/questionTypes/ShortText.tsx @@ -1,7 +1,7 @@ import QuestionDescription from '@/components/application/QuestionDescription/QuestionDescription'; import QuestionTitle from '@/components/application/QuestionTitle/QuestionTitle'; import InputField from '@/components/common/InputField/InputField'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; import { TextProps } from '@/types/application'; const ShortText = ({ diff --git a/frontend/src/constants/APPLICATION_FORM.ts b/frontend/src/constants/applicationForm.ts similarity index 100% rename from frontend/src/constants/APPLICATION_FORM.ts rename to frontend/src/constants/applicationForm.ts diff --git a/frontend/src/constants/CLUB_UNION_INFO.ts b/frontend/src/constants/clubUnionInfo.ts similarity index 100% rename from frontend/src/constants/CLUB_UNION_INFO.ts rename to frontend/src/constants/clubUnionInfo.ts diff --git a/frontend/src/constants/INITIAL_FORM_DATA.ts b/frontend/src/constants/initialFormData.ts similarity index 100% rename from frontend/src/constants/INITIAL_FORM_DATA.ts rename to frontend/src/constants/initialFormData.ts diff --git a/frontend/src/pages/AdminPage/components/QuestionBuilder/QuestionBuilder.tsx b/frontend/src/pages/AdminPage/components/QuestionBuilder/QuestionBuilder.tsx index 95ec9ca50..74b679bec 100644 --- a/frontend/src/pages/AdminPage/components/QuestionBuilder/QuestionBuilder.tsx +++ b/frontend/src/pages/AdminPage/components/QuestionBuilder/QuestionBuilder.tsx @@ -8,7 +8,7 @@ import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDow import { DROPDOWN_OPTIONS, QUESTION_LABEL_MAP, -} from '@/constants/APPLICATION_FORM'; +} from '@/constants/applicationForm'; import { QuestionBuilderProps, QuestionType } from '@/types/application'; import * as Styled from './QuestionBuilder.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx index 601d42e9b..17e8a6ee3 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.tsx @@ -4,8 +4,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { createApplication, updateApplication } from '@/apis/application'; import Button from '@/components/common/Button/Button'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; -import { APPLICATION_FORM } from '@/constants/APPLICATION_FORM'; -import INITIAL_FORM_DATA from '@/constants/INITIAL_FORM_DATA'; +import { APPLICATION_FORM } from '@/constants/applicationForm'; +import INITIAL_FORM_DATA from '@/constants/initialFormData'; import { useAdminClubContext } from '@/context/AdminClubContext'; import { useGetApplication } from '@/hooks/Queries/useApplication'; import QuestionBuilder from '@/pages/AdminPage/components/QuestionBuilder/QuestionBuilder'; diff --git a/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx b/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx index 0691b9c42..0c22b6480 100644 --- a/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx +++ b/frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx @@ -1,6 +1,6 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; -import { CLUB_UNION_MEMBERS } from '@/constants/CLUB_UNION_INFO'; +import { CLUB_UNION_MEMBERS } from '@/constants/clubUnionInfo'; import { PAGE_VIEW } from '@/constants/eventName'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; import { PageContainer } from '@/styles/PageContainer.styles'; diff --git a/frontend/src/types/application.ts b/frontend/src/types/application.ts index de59b38ca..36a9141d8 100644 --- a/frontend/src/types/application.ts +++ b/frontend/src/types/application.ts @@ -1,4 +1,4 @@ -import { QUESTION_LABEL_MAP } from '@/constants/APPLICATION_FORM'; +import { QUESTION_LABEL_MAP } from '@/constants/applicationForm'; export type QuestionType = keyof typeof QUESTION_LABEL_MAP; From 307465275894aae2a605d6c316aeeb734f7f0fa9 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:27:53 +0900 Subject: [PATCH 10/19] =?UTF-8?q?refactor:=20prettier=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.prettierrc | 13 +- frontend/.storybook/preview.ts | 2 +- frontend/config/vite.config.ts | 56 ++- frontend/src/apis/application.ts | 11 +- frontend/src/apis/auth.ts | 11 +- frontend/src/apis/club.ts | 12 +- frontend/src/apis/image.ts | 27 +- .../QuestionTitle/QuestionTitle.tsx | 8 +- .../modals/ApplicationSelectModal.styles.ts | 2 +- .../modals/ApplicationSelectModal.tsx | 33 +- .../application/questionTypes/ShortText.tsx | 1 - .../CustomDropDown/CustomDropDown.stories.tsx | 155 +++---- .../CustomTextArea/CustomTextArea.stories.tsx | 272 ++++++------ .../common/InputField/InputField.stories.tsx | 408 +++++++++--------- .../common/InputField/InputField.styles.ts | 2 +- .../components/common/Modal/Modal.styles.ts | 6 +- .../common/Modal/ModalLayout.stories.tsx | 154 +++---- .../components/common/Modal/ModalLayout.tsx | 16 +- .../common/Modal/PortalModal.stories.tsx | 34 +- .../components/common/Modal/PortalModal.tsx | 8 +- .../SearchField/SearchField.stories.tsx | 182 ++++---- .../common/Spinner/Spinner.stories.tsx | 53 +-- .../src/components/common/Spinner/Spinner.tsx | 2 +- .../AdminPage/auth/LoginTab/LoginTab.tsx | 148 +++---- .../tabs/ClubInfoEditTab/ClubInfoEditTab.tsx | 2 +- .../ClubIntroEditTab/ClubIntroEditTab.tsx | 2 +- .../RecruitEditTab/RecruitEditTab.styles.ts | 8 +- .../ApplicationFormPage.tsx | 6 +- .../pages/ClubDetailPage/ClubDetailPage.tsx | 4 +- .../ClubApplyButton/ClubApplyButton.styles.ts | 10 +- .../ClubApplyButton/ClubApplyButton.tsx | 21 +- .../ClubDetailFooter.styles.ts | 4 +- .../ClubDetailFooter/ClubDetailFooter.tsx | 2 +- .../ClubProfileCard/ClubProfileCard.styles.ts | 6 +- .../PhotoModal/PhotoModal.styles.ts | 6 +- .../components/PhotoModal/PhotoModal.tsx | 10 +- .../ShareButton/ShareButton.styles.ts | 4 +- .../components/ShareButton/ShareButton.tsx | 10 +- frontend/src/pages/MainPage/MainPage.tsx | 2 +- .../MainPage/components/Banner/Banner.tsx | 2 +- .../pages/MainPage/components/Popup/Popup.tsx | 2 +- frontend/src/utils/getDeadLineText.test.ts | 93 ++-- frontend/src/utils/getDeadLineText.ts | 9 +- 43 files changed, 918 insertions(+), 901 deletions(-) diff --git a/frontend/.prettierrc b/frontend/.prettierrc index a87ae3184..9ab256712 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -3,14 +3,7 @@ "jsxSingleQuote": true, "useTabs": false, "endOfLine": "auto", - "plugins": [ - "@ianvs/prettier-plugin-sort-imports" - ], - "importOrder": [ - "^react", - "", - "^@/", - "^[./]" - ], + "plugins": ["@ianvs/prettier-plugin-sort-imports"], + "importOrder": ["^react", "", "^@/", "^[./]"], "importOrderSortSpecifiers": true -} \ No newline at end of file +} diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts index 6a574b3ea..7275fd178 100644 --- a/frontend/.storybook/preview.ts +++ b/frontend/.storybook/preview.ts @@ -1,5 +1,5 @@ -import type { Preview } from '@storybook/react'; import { useEffect } from 'react'; +import type { Preview } from '@storybook/react'; const preview: Preview = { decorators: [ diff --git a/frontend/config/vite.config.ts b/frontend/config/vite.config.ts index cb2b190d6..231b4f088 100644 --- a/frontend/config/vite.config.ts +++ b/frontend/config/vite.config.ts @@ -1,55 +1,53 @@ import react from '@vitejs/plugin-react'; +import { visualizer } from 'rollup-plugin-visualizer'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; -import { visualizer } from 'rollup-plugin-visualizer'; const DEFAULT_PORT = 3000; export default defineConfig({ - plugins: [ - react(), - tsconfigPaths(), - ], + plugins: [react(), tsconfigPaths()], build: { rollupOptions: { output: { manualChunks(id) { - if (!id.includes("node_modules")) return; + if (!id.includes('node_modules')) return; - if (id.includes("react-router")) return "router"; - if (id.includes("react-datepicker")) return "dates"; + if (id.includes('react-router')) return 'router'; + if (id.includes('react-datepicker')) return 'dates'; if ( - id.includes("react-markdown") || - id.includes("remark") || - id.includes("rehype") || - id.includes("unified") || - id.includes("micromark") || - id.includes("mdast") || - id.includes("hast") || - id.includes("parse5") + id.includes('react-markdown') || + id.includes('remark') || + id.includes('rehype') || + id.includes('unified') || + id.includes('micromark') || + id.includes('mdast') || + id.includes('hast') || + id.includes('parse5') ) { - return "markdown"; + return 'markdown'; } if ( - id.includes("node_modules/react/") || - id.includes("node_modules/react-dom/") || - id.includes("scheduler") + id.includes('node_modules/react/') || + id.includes('node_modules/react-dom/') || + id.includes('scheduler') ) { - return "react-vendor"; + return 'react-vendor'; } - if (id.includes("zustand")) return "state"; - if (id.includes("@tanstack/react-query")) return "react-query"; + if (id.includes('zustand')) return 'state'; + if (id.includes('@tanstack/react-query')) return 'react-query'; - if (id.includes("mixpanel-browser")) return "analytics"; - if (id.includes("@sentry")) return "sentry"; + if (id.includes('mixpanel-browser')) return 'analytics'; + if (id.includes('@sentry')) return 'sentry'; - if (id.includes("framer-motion") || id.includes("motion-dom")) return "motion"; - if (id.includes("swiper")) return "swiper"; - if (id.includes("date-fns")) return "dates"; + if (id.includes('framer-motion') || id.includes('motion-dom')) + return 'motion'; + if (id.includes('swiper')) return 'swiper'; + if (id.includes('date-fns')) return 'dates'; - return "vendor"; + return 'vendor'; }, }, }, diff --git a/frontend/src/apis/application.ts b/frontend/src/apis/application.ts index b9ba1aad0..5ba52917a 100644 --- a/frontend/src/apis/application.ts +++ b/frontend/src/apis/application.ts @@ -1,7 +1,7 @@ import API_BASE_URL from '@/constants/api'; -import { secureFetch } from './auth/secureFetch'; -import { AnswerItem, ApplicationFormData } from '@/types/application'; import { UpdateApplicantParams } from '@/types/applicants'; +import { AnswerItem, ApplicationFormData } from '@/types/application'; +import { secureFetch } from './auth/secureFetch'; import { handleResponse, withErrorHandling } from './utils/apiHelpers'; export const applyToClub = async ( @@ -93,7 +93,7 @@ export const getApplicationOptions = async (clubId: string) => { return withErrorHandling(async () => { const response = await fetch(`${API_BASE_URL}/api/club/${clubId}/apply`); const data = await handleResponse(response); - + let forms: Array<{ id: string; title: string }> = []; if (data && Array.isArray(data.forms)) { forms = data.forms; @@ -123,7 +123,10 @@ export const updateApplicantDetail = async ( body: JSON.stringify(applicant), }, ); - return handleResponse(response, '지원자의 지원서 정보 수정에 실패했습니다.'); + return handleResponse( + response, + '지원자의 지원서 정보 수정에 실패했습니다.', + ); }, '지원자의 지원서 정보 수정 중 오류 발생:'); }; diff --git a/frontend/src/apis/auth.ts b/frontend/src/apis/auth.ts index d66c4d3e7..0b78c0275 100644 --- a/frontend/src/apis/auth.ts +++ b/frontend/src/apis/auth.ts @@ -40,19 +40,16 @@ export const logout = async (): Promise => { method: 'GET', credentials: 'include', }); - + await handleResponse(response, '로그아웃에 실패하였습니다.'); }, '로그아웃 중 오류 발생'); }; export const getClubIdByToken = async (): Promise => { return withErrorHandling(async () => { - const response = await secureFetch( - `${API_BASE_URL}/auth/user/find/club`, - { - method: 'POST', - }, - ); + const response = await secureFetch(`${API_BASE_URL}/auth/user/find/club`, { + method: 'POST', + }); const data = await handleResponse(response, '인증에 실패했습니다.'); if (!data?.clubId) { throw new Error('ClubId를 가져올 수 없습니다.'); diff --git a/frontend/src/apis/club.ts b/frontend/src/apis/club.ts index d805f7e4b..216ba6937 100644 --- a/frontend/src/apis/club.ts +++ b/frontend/src/apis/club.ts @@ -1,12 +1,15 @@ import API_BASE_URL from '@/constants/api'; +import { ClubDescription, ClubDetail } from '@/types/club'; import { secureFetch } from './auth/secureFetch'; -import { ClubDetail, ClubDescription } from '@/types/club'; import { handleResponse, withErrorHandling } from './utils/apiHelpers'; export const getClubDetail = async (clubId: string): Promise => { return withErrorHandling(async () => { const response = await fetch(`${API_BASE_URL}/api/club/${clubId}`); - const data = await handleResponse(response, '클럽 정보를 불러오는데 실패했습니다.'); + const data = await handleResponse( + response, + '클럽 정보를 불러오는데 실패했습니다.', + ); if (!data?.club) { throw new Error('클럽 정보를 가져올 수 없습니다.'); } @@ -31,7 +34,10 @@ export const getClubList = async ( url.search = params.toString(); const response = await fetch(url); - const data = await handleResponse(response, '클럽 데이터를 불러오는데 실패했습니다.'); + const data = await handleResponse( + response, + '클럽 데이터를 불러오는데 실패했습니다.', + ); if (!data) { throw new Error('클럽 데이터를 가져올 수 없습니다.'); diff --git a/frontend/src/apis/image.ts b/frontend/src/apis/image.ts index acd2e6bf6..46b1d9f9b 100644 --- a/frontend/src/apis/image.ts +++ b/frontend/src/apis/image.ts @@ -1,5 +1,5 @@ -import { secureFetch } from './auth/secureFetch'; import API_BASE_URL from '@/constants/api'; +import { secureFetch } from './auth/secureFetch'; import { handleResponse, withErrorHandling } from './utils/apiHelpers'; interface PresignedData { @@ -43,7 +43,10 @@ export const coverApi = { body: JSON.stringify({ fileName, contentType }), }, ); - return handleResponse(response, `커버 업로드 URL 생성 실패 : ${response.status}`); + return handleResponse( + response, + `커버 업로드 URL 생성 실패 : ${response.status}`, + ); }, '커버 업로드 URL 생성 중 오류 발생'); }, @@ -57,7 +60,10 @@ export const coverApi = { body: JSON.stringify({ fileUrl }), }, ); - await handleResponse(response, `커버 업로드 완료 처리 실패 : ${response.status}`); + await handleResponse( + response, + `커버 업로드 완료 처리 실패 : ${response.status}`, + ); }, '커버 업로드 완료 처리 중 오류 발생'); }, @@ -89,7 +95,10 @@ export const feedApi = { body: JSON.stringify(uploadRequests), }, ); - return handleResponse(response, `피드 업로드 URL 생성 실패 : ${response.status}`); + return handleResponse( + response, + `피드 업로드 URL 생성 실패 : ${response.status}`, + ); }, '피드 업로드 URL 생성 중 오류 발생'); }, @@ -124,7 +133,10 @@ export const logoApi = { body: JSON.stringify({ fileName, contentType }), }, ); - return handleResponse(response, `업로드 URL 생성 실패 : ${response.status}`); + return handleResponse( + response, + `업로드 URL 생성 실패 : ${response.status}`, + ); }, '로고 업로드 URL 생성 중 오류 발생'); }, @@ -138,7 +150,10 @@ export const logoApi = { body: JSON.stringify({ fileUrl }), }, ); - await handleResponse(response, `업로드 완료 처리 실패 : ${response.status}`); + await handleResponse( + response, + `업로드 완료 처리 실패 : ${response.status}`, + ); }, '로고 업로드 완료 처리 중 오류 발생'); }, diff --git a/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx b/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx index e8c0f2f99..9cee46f44 100644 --- a/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx +++ b/frontend/src/components/application/QuestionTitle/QuestionTitle.tsx @@ -38,7 +38,9 @@ const QuestionTitle = ({ {mode === 'answer' ? ( {title} - {required && } + {required && ( + + )} ) : ( @@ -55,7 +57,9 @@ const QuestionTitle = ({ placeholder={APPLICATION_FORM.QUESTION_TITLE.placeholder} aria-required={required} /> - {required && } + {required && ( + + )} )} diff --git a/frontend/src/components/application/modals/ApplicationSelectModal.styles.ts b/frontend/src/components/application/modals/ApplicationSelectModal.styles.ts index d7ad5158c..5a4e412b9 100644 --- a/frontend/src/components/application/modals/ApplicationSelectModal.styles.ts +++ b/frontend/src/components/application/modals/ApplicationSelectModal.styles.ts @@ -1,5 +1,5 @@ -import { colors } from '@/styles/theme/colors'; import styled from 'styled-components'; +import { colors } from '@/styles/theme/colors'; export const EmptyMessage = styled.div` padding: 16px 8px; diff --git a/frontend/src/components/application/modals/ApplicationSelectModal.tsx b/frontend/src/components/application/modals/ApplicationSelectModal.tsx index 3a1b05e13..75109cfa7 100644 --- a/frontend/src/components/application/modals/ApplicationSelectModal.tsx +++ b/frontend/src/components/application/modals/ApplicationSelectModal.tsx @@ -1,7 +1,7 @@ +import ModalLayout from '@/components/common/Modal/ModalLayout'; +import PortalModal from '@/components/common/Modal/PortalModal'; import { ApplicationForm } from '@/types/application'; import * as Styled from './ApplicationSelectModal.styles'; -import PortalModal from '@/components/common/Modal/PortalModal'; -import ModalLayout from '@/components/common/Modal/ModalLayout'; export interface ApplicationSelectModalProps { isOpen: boolean; @@ -15,15 +15,13 @@ interface ApplicationOptionsProps { onOptionSelect: (application: ApplicationForm) => void; } -const ApplicationOptions = ({ - applicationOptions, +const ApplicationOptions = ({ + applicationOptions, onOptionSelect, - }: ApplicationOptionsProps) => { +}: ApplicationOptionsProps) => { if (applicationOptions.length === 0) { return ( - - 지원 가능한 분야가 없습니다. - + 지원 가능한 분야가 없습니다. ); } @@ -32,7 +30,9 @@ const ApplicationOptions = ({ {applicationOptions.map((application) => ( {onOptionSelect(application)}} + onClick={() => { + onOptionSelect(application); + }} > {application.title} @@ -48,15 +48,12 @@ const ApplicationSelectModal = ({ onOptionSelect, }: ApplicationSelectModalProps) => { return ( - - - + + + ); diff --git a/frontend/src/components/application/questionTypes/ShortText.tsx b/frontend/src/components/application/questionTypes/ShortText.tsx index abd5219ad..3702da555 100644 --- a/frontend/src/components/application/questionTypes/ShortText.tsx +++ b/frontend/src/components/application/questionTypes/ShortText.tsx @@ -15,7 +15,6 @@ const ShortText = ({ onTitleChange, onDescriptionChange, }: TextProps) => { - return ( <> ; export default meta; @@ -50,59 +50,60 @@ const StyledTrigger = styled.button` `; const OPTIONS = [ - { label: '옵션 1', value: 'option1' }, - { label: '옵션 2', value: 'option2' }, - { label: '옵션 3', value: 'option3' }, + { label: '옵션 1', value: 'option1' }, + { label: '옵션 2', value: 'option2' }, + { label: '옵션 3', value: 'option3' }, ]; export const Default: Story = { - args: { - open: false, - selected: 'option1', - options: OPTIONS, - onToggle: () => { }, - onSelect: () => { }, - children: null, - }, - render: (args) => { - const [isOpen, setIsOpen] = useState(args.open); - const [selected, setSelected] = useState(args.selected); + args: { + open: false, + selected: 'option1', + options: OPTIONS, + onToggle: () => {}, + onSelect: () => {}, + children: null, + }, + render: (args) => { + const [isOpen, setIsOpen] = useState(args.open); + const [selected, setSelected] = useState(args.selected); - const handleSelect = (value: string) => { - setSelected(value); - args.onSelect(value); - }; + const handleSelect = (value: string) => { + setSelected(value); + args.onSelect(value); + }; - const onToggleWrapper = (currentOpenState: boolean) => { - setIsOpen(!currentOpenState); - args.onToggle(!currentOpenState); - } + const onToggleWrapper = (currentOpenState: boolean) => { + setIsOpen(!currentOpenState); + args.onToggle(!currentOpenState); + }; - const selectedLabel = OPTIONS.find(opt => opt.value === selected)?.label || '선택하세요'; + const selectedLabel = + OPTIONS.find((opt) => opt.value === selected)?.label || '선택하세요'; - return ( - - - - {selectedLabel} - - - - - {OPTIONS.map((option) => ( - - {option.label} - - ))} - - - ); - }, -}; \ No newline at end of file + return ( + + + + {selectedLabel} + + + + + {OPTIONS.map((option) => ( + + {option.label} + + ))} + + + ); + }, +}; diff --git a/frontend/src/components/common/CustomTextArea/CustomTextArea.stories.tsx b/frontend/src/components/common/CustomTextArea/CustomTextArea.stories.tsx index b96e3da87..fcbd1fe65 100644 --- a/frontend/src/components/common/CustomTextArea/CustomTextArea.stories.tsx +++ b/frontend/src/components/common/CustomTextArea/CustomTextArea.stories.tsx @@ -1,161 +1,161 @@ +import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import CustomTextArea from './CustomTextArea'; -import { useState } from 'react'; const meta = { - title: 'Components/Common/CustomTextArea', - component: CustomTextArea, - parameters: { - layout: 'centered', + title: 'Components/Common/CustomTextArea', + component: CustomTextArea, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + value: { + control: 'text', + description: '텍스트 영역의 값입니다.', + }, + onChange: { + action: 'changed', + description: '값이 변경될 때 호출되는 함수입니다.', + }, + placeholder: { + control: 'text', + description: '텍스트 영역의 플레이스홀더입니다.', + }, + label: { + control: 'text', + description: '텍스트 영역 상단에 표시되는 라벨입니다.', + }, + width: { + control: 'text', + description: '텍스트 영역의 너비입니다.', + }, + disabled: { + control: 'boolean', + description: '비활성화 여부입니다.', + }, + isError: { + control: 'boolean', + description: '에러 상태 여부입니다.', + }, + helperText: { + control: 'text', + description: '하단에 표시되는 도움말 텍스트입니다 (에러 시 표시).', + }, + showMaxChar: { + control: 'boolean', + description: '최대 글자수 표시 여부입니다.', }, - tags: ['autodocs'], - argTypes: { - value: { - control: 'text', - description: '텍스트 영역의 값입니다.', - }, - onChange: { - action: 'changed', - description: '값이 변경될 때 호출되는 함수입니다.', - }, - placeholder: { - control: 'text', - description: '텍스트 영역의 플레이스홀더입니다.', - }, - label: { - control: 'text', - description: '텍스트 영역 상단에 표시되는 라벨입니다.', - }, - width: { - control: 'text', - description: '텍스트 영역의 너비입니다.', - }, - disabled: { - control: 'boolean', - description: '비활성화 여부입니다.', - }, - isError: { - control: 'boolean', - description: '에러 상태 여부입니다.', - }, - helperText: { - control: 'text', - description: '하단에 표시되는 도움말 텍스트입니다 (에러 시 표시).', - }, - showMaxChar: { - control: 'boolean', - description: '최대 글자수 표시 여부입니다.', - }, - maxLength: { - control: 'number', - description: '최대 글자수 제한입니다.', - }, + maxLength: { + control: 'number', + description: '최대 글자수 제한입니다.', }, + }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { - args: { - placeholder: '내용을 입력하세요', - width: '300px', - value: '', - onChange: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - /> - ); - }, + args: { + placeholder: '내용을 입력하세요', + width: '300px', + value: '', + onChange: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + /> + ); + }, }; export const WithLabel: Story = { - args: { - label: '자기소개', - placeholder: '자기소개를 입력하세요', - width: '300px', - value: '', - onChange: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - /> - ); - }, + args: { + label: '자기소개', + placeholder: '자기소개를 입력하세요', + width: '300px', + value: '', + onChange: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + /> + ); + }, }; export const ErrorState: Story = { - args: { - label: '지원동기', - value: '너무 짧습니다.', - isError: true, - helperText: '10자 이상 입력해주세요.', - width: '300px', - onChange: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - /> - ); - }, + args: { + label: '지원동기', + value: '너무 짧습니다.', + isError: true, + helperText: '10자 이상 입력해주세요.', + width: '300px', + onChange: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + /> + ); + }, }; export const WithMaxLength: Story = { - args: { - label: '문의 내용', - placeholder: '100자 이내로 입력해주세요', - maxLength: 100, - showMaxChar: true, - width: '300px', - value: '', - onChange: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - /> - ); - }, + args: { + label: '문의 내용', + placeholder: '100자 이내로 입력해주세요', + maxLength: 100, + showMaxChar: true, + width: '300px', + value: '', + onChange: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + /> + ); + }, }; export const Disabled: Story = { - args: { - label: '피드백', - value: '이미 제출된 피드백입니다.', - disabled: true, - width: '300px', - onChange: () => { }, - }, + args: { + label: '피드백', + value: '이미 제출된 피드백입니다.', + disabled: true, + width: '300px', + onChange: () => {}, + }, }; diff --git a/frontend/src/components/common/InputField/InputField.stories.tsx b/frontend/src/components/common/InputField/InputField.stories.tsx index 7059fd414..191148d5d 100644 --- a/frontend/src/components/common/InputField/InputField.stories.tsx +++ b/frontend/src/components/common/InputField/InputField.stories.tsx @@ -1,231 +1,231 @@ +import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import InputField from './InputField'; -import { useState } from 'react'; const meta = { - title: 'Components/Common/InputField', - component: InputField, - parameters: { - layout: 'centered', + title: 'Components/Common/InputField', + component: InputField, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + value: { + control: 'text', + description: '입력 필드의 값입니다.', + }, + onChange: { + action: 'changed', + description: '값이 변경될 때 호출되는 함수입니다.', + }, + onClear: { + action: 'cleared', + description: '삭제 버튼 클릭 시 호출되는 함수입니다.', + }, + placeholder: { + control: 'text', + description: '입력 필드의 플레이스홀더입니다.', + }, + label: { + control: 'text', + description: '입력 필드 상단에 표시되는 라벨입니다.', + }, + type: { + control: 'radio', + options: ['text', 'password'], + description: '입력 필드의 타입입니다.', + }, + width: { + control: 'text', + description: '입력 필드의 너비입니다.', + }, + disabled: { + control: 'boolean', + description: '비활성화 여부입니다.', + }, + isError: { + control: 'boolean', + description: '에러 상태 여부입니다.', + }, + isSuccess: { + control: 'boolean', + description: '성공 상태 여부입니다.', + }, + helperText: { + control: 'text', + description: '하단에 표시되는 도움말 텍스트입니다 (에러 시 표시).', + }, + showClearButton: { + control: 'boolean', + description: '삭제 버튼 표시 여부입니다.', }, - tags: ['autodocs'], - argTypes: { - value: { - control: 'text', - description: '입력 필드의 값입니다.', - }, - onChange: { - action: 'changed', - description: '값이 변경될 때 호출되는 함수입니다.', - }, - onClear: { - action: 'cleared', - description: '삭제 버튼 클릭 시 호출되는 함수입니다.', - }, - placeholder: { - control: 'text', - description: '입력 필드의 플레이스홀더입니다.', - }, - label: { - control: 'text', - description: '입력 필드 상단에 표시되는 라벨입니다.', - }, - type: { - control: 'radio', - options: ['text', 'password'], - description: '입력 필드의 타입입니다.', - }, - width: { - control: 'text', - description: '입력 필드의 너비입니다.', - }, - disabled: { - control: 'boolean', - description: '비활성화 여부입니다.', - }, - isError: { - control: 'boolean', - description: '에러 상태 여부입니다.', - }, - isSuccess: { - control: 'boolean', - description: '성공 상태 여부입니다.', - }, - helperText: { - control: 'text', - description: '하단에 표시되는 도움말 텍스트입니다 (에러 시 표시).', - }, - showClearButton: { - control: 'boolean', - description: '삭제 버튼 표시 여부입니다.', - }, - showMaxChar: { - control: 'boolean', - description: '최대 글자수 표시 여부입니다.', - }, - maxLength: { - control: 'number', - description: '최대 글자수 제한입니다.', - }, - readOnly: { - control: 'boolean', - description: '읽기 전용 여부입니다.', - }, + showMaxChar: { + control: 'boolean', + description: '최대 글자수 표시 여부입니다.', }, + maxLength: { + control: 'number', + description: '최대 글자수 제한입니다.', + }, + readOnly: { + control: 'boolean', + description: '읽기 전용 여부입니다.', + }, + }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { - args: { - placeholder: '텍스트를 입력하세요', - width: '300px', - value: '', - onChange: () => { }, - onClear: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - onClear={() => { - setValue(''); - args.onClear?.(); - }} - /> - ); - }, + args: { + placeholder: '텍스트를 입력하세요', + width: '300px', + value: '', + onChange: () => {}, + onClear: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + onClear={() => { + setValue(''); + args.onClear?.(); + }} + /> + ); + }, }; export const WithLabel: Story = { - args: { - label: '이메일', - placeholder: 'example@email.com', - width: '300px', - value: '', - onChange: () => { }, - onClear: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - onClear={() => { - setValue(''); - args.onClear?.(); - }} - /> - ); - }, + args: { + label: '이메일', + placeholder: 'example@email.com', + width: '300px', + value: '', + onChange: () => {}, + onClear: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + onClear={() => { + setValue(''); + args.onClear?.(); + }} + /> + ); + }, }; export const Password: Story = { - args: { - type: 'password', - label: '비밀번호', - placeholder: '비밀번호를 입력하세요', - width: '300px', - value: '', - onChange: () => { }, - onClear: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - onClear={() => { - setValue(''); - args.onClear?.(); - }} - /> - ); - }, + args: { + type: 'password', + label: '비밀번호', + placeholder: '비밀번호를 입력하세요', + width: '300px', + value: '', + onChange: () => {}, + onClear: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + onClear={() => { + setValue(''); + args.onClear?.(); + }} + /> + ); + }, }; export const ErrorState: Story = { - args: { - label: '닉네임', - value: '이미 사용중인 닉네임입니다', - isError: true, - helperText: '이미 사용중인 닉네임입니다.', - width: '300px', - onChange: () => { }, - onClear: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - onClear={() => { - setValue(''); - args.onClear?.(); - }} - /> - ); - }, + args: { + label: '닉네임', + value: '이미 사용중인 닉네임입니다', + isError: true, + helperText: '이미 사용중인 닉네임입니다.', + width: '300px', + onChange: () => {}, + onClear: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + onClear={() => { + setValue(''); + args.onClear?.(); + }} + /> + ); + }, }; export const WithMaxLength: Story = { - args: { - label: '한줄 소개', - placeholder: '20자 이내로 입력해주세요', - maxLength: 20, - showMaxChar: true, - width: '300px', - value: '', - onChange: () => { }, - onClear: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value || ''); - return ( - { - setValue(e.target.value); - args.onChange?.(e); - }} - onClear={() => { - setValue(''); - args.onClear?.(); - }} - /> - ); - }, + args: { + label: '한줄 소개', + placeholder: '20자 이내로 입력해주세요', + maxLength: 20, + showMaxChar: true, + width: '300px', + value: '', + onChange: () => {}, + onClear: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value || ''); + return ( + { + setValue(e.target.value); + args.onChange?.(e); + }} + onClear={() => { + setValue(''); + args.onClear?.(); + }} + /> + ); + }, }; export const Disabled: Story = { - args: { - label: '아이디', - value: 'disabled_user', - disabled: true, - width: '300px', - onChange: () => { }, - onClear: () => { }, - }, + args: { + label: '아이디', + value: 'disabled_user', + disabled: true, + width: '300px', + onChange: () => {}, + onClear: () => {}, + }, }; diff --git a/frontend/src/components/common/InputField/InputField.styles.ts b/frontend/src/components/common/InputField/InputField.styles.ts index 7dbc653f1..794632e2e 100644 --- a/frontend/src/components/common/InputField/InputField.styles.ts +++ b/frontend/src/components/common/InputField/InputField.styles.ts @@ -1,6 +1,6 @@ import styled from 'styled-components'; -import { colors } from '@/styles/theme/colors'; import { media } from '@/styles/mediaQuery'; +import { colors } from '@/styles/theme/colors'; export const InputContainer = styled.div<{ width: string; readOnly?: boolean }>` width: ${(props) => props.width}; diff --git a/frontend/src/components/common/Modal/Modal.styles.ts b/frontend/src/components/common/Modal/Modal.styles.ts index 3fccb6494..22a1486c2 100644 --- a/frontend/src/components/common/Modal/Modal.styles.ts +++ b/frontend/src/components/common/Modal/Modal.styles.ts @@ -1,6 +1,6 @@ import styled from 'styled-components'; -import { Z_INDEX } from '@/styles/zIndex'; import { colors } from '@/styles/theme/colors'; +import { Z_INDEX } from '@/styles/zIndex'; export const Overlay = styled.div` inset: 0; @@ -18,7 +18,7 @@ export const ContentWrapper = styled.div` outline: none; display: flex; justify-content: center; - width: 100%; + width: 100%; max-width: fit-content; `; @@ -30,7 +30,7 @@ export const StandardLayout = styled.div<{ $width?: string }>` box-shadow: 0 18px 44px rgba(0, 0, 0, 0.22); display: flex; flex-direction: column; - width: ${({ $width }) => $width || '400px'}; + width: ${({ $width }) => $width || '400px'}; max-width: 100%; `; diff --git a/frontend/src/components/common/Modal/ModalLayout.stories.tsx b/frontend/src/components/common/Modal/ModalLayout.stories.tsx index 80569ab92..25b433b83 100644 --- a/frontend/src/components/common/Modal/ModalLayout.stories.tsx +++ b/frontend/src/components/common/Modal/ModalLayout.stories.tsx @@ -2,33 +2,33 @@ import type { Meta, StoryObj } from '@storybook/react'; import ModalLayout from './ModalLayout'; const meta = { - title: 'Components/Common/ModalLayout', - component: ModalLayout, - parameters: { - layout: 'centered', + title: 'Components/Common/ModalLayout', + component: ModalLayout, + parameters: { + layout: 'centered', + }, + argTypes: { + onClose: { + action: 'closed', + description: '모달 닫기 버튼 클릭 시 호출되는 함수입니다.', }, - argTypes: { - onClose: { - action: 'closed', - description: '모달 닫기 버튼 클릭 시 호출되는 함수입니다.', - }, - title: { - control: 'text', - description: '모달의 제목입니다.', - }, - description: { - control: 'text', - description: '모달의 설명 텍스트입니다.', - }, - children: { - control: 'text', - description: '모달 내부에 렌더링될 컨텐츠입니다.', - }, - width: { - control: 'text', - description: '모달의 너비를 설정합니다.', - }, + title: { + control: 'text', + description: '모달의 제목입니다.', }, + description: { + control: 'text', + description: '모달의 설명 텍스트입니다.', + }, + children: { + control: 'text', + description: '모달 내부에 렌더링될 컨텐츠입니다.', + }, + width: { + control: 'text', + description: '모달의 너비를 설정합니다.', + }, + }, } satisfies Meta; export default meta; @@ -36,64 +36,72 @@ type Story = StoryObj; // 기본 모달 스토리 export const Default: Story = { - args: { - title: '기본 모달 레이아웃', - description: '이것은 기본 모달 레이아웃입니다.', - children: '모달 내용이 여기에 들어갑니다.', - width: '400px', - }, + args: { + title: '기본 모달 레이아웃', + description: '이것은 기본 모달 레이아웃입니다.', + children: '모달 내용이 여기에 들어갑니다.', + width: '400px', + }, }; // 너비가 다른 모달 스토리 export const WideModal: Story = { - args: { - title: '넓은 모달 레이아웃', - description: '이것은 너비가 600px인 모달 레이아웃입니다.', - children: '모달 내용이 여기에 들어갑니다.', - width: '600px', - }, + args: { + title: '넓은 모달 레이아웃', + description: '이것은 너비가 600px인 모달 레이아웃입니다.', + children: '모달 내용이 여기에 들어갑니다.', + width: '600px', + }, }; // 설명이 없는 모달 스토리 export const NoDescription: Story = { - args: { - title: '설명이 없는 모달 레이아웃', - children: '설명 없이 제목과 내용만 있는 모달입니다.', - width: '400px', - }, + args: { + title: '설명이 없는 모달 레이아웃', + children: '설명 없이 제목과 내용만 있는 모달입니다.', + width: '400px', + }, }; // 내용이 긴 모달 스토리 export const LongContent: Story = { - args: { - onClose: () => { }, - title: '내용이 긴 모달 레이아웃', - description: '스크롤이 필요한 경우를 테스트합니다.', - children: ( -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -

-

- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. -

-

- Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, - eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. -

-

- At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti - quos dolores et quas molestias excepturi sint occaecati cupiditate non provident. -

+ args: { + onClose: () => {}, + title: '내용이 긴 모달 레이아웃', + description: '스크롤이 필요한 경우를 테스트합니다.', + children: ( +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad + minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. +

+

+ Duis aute irure dolor in reprehenderit in voluptate velit esse cillum + dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est + laborum. +

+

+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae + ab illo inventore veritatis et quasi architecto beatae vitae dicta + sunt explicabo. +

+

+ At vero eos et accusamus et iusto odio dignissimos ducimus qui + blanditiis praesentium voluptatum deleniti atque corrupti quos dolores + et quas molestias excepturi sint occaecati cupiditate non provident. +

-

- Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, - omnis voluptas assumenda est, omnis dolor repellendus. -

-
- ), - width: '400px', - }, -}; \ No newline at end of file +

+ Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil + impedit quo minus id quod maxime placeat facere possimus, omnis + voluptas assumenda est, omnis dolor repellendus. +

+
+ ), + width: '400px', + }, +}; diff --git a/frontend/src/components/common/Modal/ModalLayout.tsx b/frontend/src/components/common/Modal/ModalLayout.tsx index e8b94506a..561e1b237 100644 --- a/frontend/src/components/common/Modal/ModalLayout.tsx +++ b/frontend/src/components/common/Modal/ModalLayout.tsx @@ -17,21 +17,17 @@ const ModalLayout = ({ width, }: ModalLayoutProps) => { return ( - + {(title || onClose) && ( {title && {title}} {onClose && ( - - ✕ + ✕ )} diff --git a/frontend/src/components/common/Modal/PortalModal.stories.tsx b/frontend/src/components/common/Modal/PortalModal.stories.tsx index acb04dd21..12702dc06 100644 --- a/frontend/src/components/common/Modal/PortalModal.stories.tsx +++ b/frontend/src/components/common/Modal/PortalModal.stories.tsx @@ -40,7 +40,9 @@ const ModalContent = ({ text: string; onClose: () => void; }) => ( -
+
{text}
@@ -62,20 +64,16 @@ export const Default: Story = { const handleClose = () => { setIsOpen(false); args.onClose(); - } + }; return ( <> - - + + ); @@ -96,23 +94,19 @@ export const NoBackdropClose: Story = { const handleClose = () => { setOpen(false); args.onClose(); - } + }; return ( <> - - + ); - } + }, }; diff --git a/frontend/src/components/common/Modal/PortalModal.tsx b/frontend/src/components/common/Modal/PortalModal.tsx index 8f29293bf..c2b5705b3 100644 --- a/frontend/src/components/common/Modal/PortalModal.tsx +++ b/frontend/src/components/common/Modal/PortalModal.tsx @@ -17,7 +17,9 @@ const PortalModal = ({ }: PortalModalProps) => { useEffect(() => { if (isOpen) document.body.style.overflow = 'hidden'; - return () => { document.body.style.overflow = ''; }; + return () => { + document.body.style.overflow = ''; + }; }, [isOpen]); if (!isOpen) return null; @@ -27,7 +29,9 @@ const PortalModal = ({ return createPortal( { if (closeOnBackdrop) onClose();}} + onClick={() => { + if (closeOnBackdrop) onClose(); + }} > ) => e.stopPropagation()} diff --git a/frontend/src/components/common/SearchField/SearchField.stories.tsx b/frontend/src/components/common/SearchField/SearchField.stories.tsx index bfc5e643f..d36af9cc8 100644 --- a/frontend/src/components/common/SearchField/SearchField.stories.tsx +++ b/frontend/src/components/common/SearchField/SearchField.stories.tsx @@ -1,40 +1,40 @@ +import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import SearchField from './SearchField'; -import { useState } from 'react'; const meta = { - title: 'Components/Common/SearchField', - component: SearchField, - parameters: { - layout: 'centered', + title: 'Components/Common/SearchField', + component: SearchField, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + value: { + control: 'text', + description: '검색어 입력값입니다.', + }, + onChange: { + action: 'changed', + description: '입력값이 변경될 때 호출되는 함수입니다.', + }, + onSubmit: { + action: 'submitted', + description: '검색 제출 시 호출되는 함수입니다.', + }, + placeholder: { + control: 'text', + description: '입력창의 플레이스홀더 텍스트입니다.', + }, + ariaLabel: { + control: 'text', + description: '접근성을 위한 aria-label 속성입니다.', }, - tags: ['autodocs'], - argTypes: { - value: { - control: 'text', - description: '검색어 입력값입니다.', - }, - onChange: { - action: 'changed', - description: '입력값이 변경될 때 호출되는 함수입니다.', - }, - onSubmit: { - action: 'submitted', - description: '검색 제출 시 호출되는 함수입니다.', - }, - placeholder: { - control: 'text', - description: '입력창의 플레이스홀더 텍스트입니다.', - }, - ariaLabel: { - control: 'text', - description: '접근성을 위한 aria-label 속성입니다.', - }, - autoBlur: { - control: 'boolean', - description: '제출 후 자동으로 포커스를 해제할지 여부입니다.', - }, + autoBlur: { + control: 'boolean', + description: '제출 후 자동으로 포커스를 해제할지 여부입니다.', }, + }, } satisfies Meta; export default meta; @@ -42,75 +42,75 @@ type Story = StoryObj; // 기본 검색창 스토리 export const Default: Story = { - args: { - value: '', - placeholder: '동아리 이름을 입력하세요', - autoBlur: true, - onChange: () => { }, - onSubmit: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value); + args: { + value: '', + placeholder: '동아리 이름을 입력하세요', + autoBlur: true, + onChange: () => {}, + onSubmit: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value); - return ( - { - setValue(newValue); - args.onChange(newValue); - }} - /> - ); - }, + return ( + { + setValue(newValue); + args.onChange(newValue); + }} + /> + ); + }, }; // 값이 미리 채워진 상태 export const WithValue: Story = { - args: { - value: '밴드 동아리', - placeholder: '검색어를 입력하세요', - autoBlur: true, - onChange: () => { }, - onSubmit: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value); + args: { + value: '밴드 동아리', + placeholder: '검색어를 입력하세요', + autoBlur: true, + onChange: () => {}, + onSubmit: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value); - return ( - { - setValue(newValue); - args.onChange(newValue); - }} - /> - ); - }, + return ( + { + setValue(newValue); + args.onChange(newValue); + }} + /> + ); + }, }; // 커스텀 플레이스홀더 export const CustomPlaceholder: Story = { - args: { - value: '', - placeholder: '원하는 태그를 검색해보세요 (#음악, #운동)', - autoBlur: true, - onChange: () => { }, - onSubmit: () => { }, - }, - render: (args) => { - const [value, setValue] = useState(args.value); + args: { + value: '', + placeholder: '원하는 태그를 검색해보세요 (#음악, #운동)', + autoBlur: true, + onChange: () => {}, + onSubmit: () => {}, + }, + render: (args) => { + const [value, setValue] = useState(args.value); - return ( - { - setValue(newValue); - args.onChange(newValue); - }} - /> - ); - }, + return ( + { + setValue(newValue); + args.onChange(newValue); + }} + /> + ); + }, }; diff --git a/frontend/src/components/common/Spinner/Spinner.stories.tsx b/frontend/src/components/common/Spinner/Spinner.stories.tsx index d7ea6c239..e219d02bc 100644 --- a/frontend/src/components/common/Spinner/Spinner.stories.tsx +++ b/frontend/src/components/common/Spinner/Spinner.stories.tsx @@ -2,18 +2,18 @@ import type { Meta, StoryObj } from '@storybook/react'; import Spinner from './Spinner'; const meta = { - title: 'Components/Common/Spinner', - component: Spinner, - parameters: { - layout: 'centered', - }, - tags: ['autodocs'], - argTypes: { - height: { - control: 'text', - description: '스피너 컨테이너의 높이입니다. (기본값: 100vh)', - }, + title: 'Components/Common/Spinner', + component: Spinner, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + height: { + control: 'text', + description: '스피너 컨테이너의 높이입니다. (기본값: 100vh)', }, + }, } satisfies Meta; export default meta; @@ -21,24 +21,25 @@ type Story = StoryObj; // 기본 스피너 (전체 화면 높이) export const Default: Story = { - args: {}, + args: {}, }; // 커스텀 높이 스피너 export const CustomHeight: Story = { - args: { - height: '200px', - }, - parameters: { - docs: { - description: { - story: '특정 영역 안에서 로딩을 표시할 때 높이를 조절하여 사용할 수 있습니다.', - }, - }, + args: { + height: '200px', + }, + parameters: { + docs: { + description: { + story: + '특정 영역 안에서 로딩을 표시할 때 높이를 조절하여 사용할 수 있습니다.', + }, }, - render: (args) => ( -
- -
- ), + }, + render: (args) => ( +
+ +
+ ), }; diff --git a/frontend/src/components/common/Spinner/Spinner.tsx b/frontend/src/components/common/Spinner/Spinner.tsx index cf9c50bfc..baedf1f2d 100644 --- a/frontend/src/components/common/Spinner/Spinner.tsx +++ b/frontend/src/components/common/Spinner/Spinner.tsx @@ -17,7 +17,7 @@ const spin = keyframes` const SpinnerWrapper = styled.div.attrs(() => ({ role: 'status', 'aria-label': '로딩 중', -})) ` +}))` display: flex; justify-content: center; align-items: center; diff --git a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx index 0776e7af0..334e9f31e 100644 --- a/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx +++ b/frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx @@ -3,13 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { login } from '@/apis/auth'; import moadong_name_logo from '@/assets/images/logos/moadong_name_logo.svg'; import Button from '@/components/common/Button/Button'; +import Header from '@/components/common/Header/Header'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import useAuth from '@/hooks/useAuth'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import useAuth from '@/hooks/useAuth'; import * as Styled from './LoginTab.styles'; -import Header from '@/components/common/Header/Header'; const LoginTab = () => { useTrackPageView(PAGE_VIEW.LOGIN_PAGE); @@ -54,80 +54,80 @@ const LoginTab = () => { return ( <> -
- - - - Log in - { - e.preventDefault(); - handleLogin(); - }} - > - - setUserId(e.target.value)} - /> - setPassword(e.target.value)} - /> - - - - - - - - - { - trackEvent(ADMIN_EVENT.SIGNUP_BUTTON_CLICKED); - alert( - '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', - ); +
+ + + + Log in + { + e.preventDefault(); + handleLogin(); }} > - 회원가입 - - | - { - trackEvent(ADMIN_EVENT.FORGOT_ID_BUTTON_CLICKED); - alert( - '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', - ); - }} - > - 아이디 찾기 - - | - { - trackEvent(ADMIN_EVENT.FORGOT_PASSWORD_BUTTON_CLICKED); - alert( - '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', - ); - }} - > - 비밀번호 찾기 - - - - + + setUserId(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + + + + + + + { + trackEvent(ADMIN_EVENT.SIGNUP_BUTTON_CLICKED); + alert( + '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', + ); + }} + > + 회원가입 + + | + { + trackEvent(ADMIN_EVENT.FORGOT_ID_BUTTON_CLICKED); + alert( + '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', + ); + }} + > + 아이디 찾기 + + | + { + trackEvent(ADMIN_EVENT.FORGOT_PASSWORD_BUTTON_CLICKED); + alert( + '해당 기능은 아직 준비 중이에요.\n필요하신 경우 관리자에게 문의해주세요☺', + ); + }} + > + 비밀번호 찾기 + + + + ); }; diff --git a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx index d835c08ad..bdfc5e9c6 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx @@ -5,9 +5,9 @@ import Button from '@/components/common/Button/Button'; import InputField from '@/components/common/InputField/InputField'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; import { SNS_CONFIG } from '@/constants/snsConfig'; -import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import ClubCoverEditor from '@/pages/AdminPage/components/ClubCoverEditor/ClubCoverEditor'; import ClubLogoEditor from '@/pages/AdminPage/components/ClubLogoEditor/ClubLogoEditor'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; diff --git a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx index bd6f782a4..404eab245 100644 --- a/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTab.tsx @@ -4,9 +4,9 @@ import { useQueryClient } from '@tanstack/react-query'; import Button from '@/components/common/Button/Button'; import CustomTextArea from '@/components/common/CustomTextArea/CustomTextArea'; import { ADMIN_EVENT, PAGE_VIEW } from '@/constants/eventName'; -import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useUpdateClubDetail } from '@/hooks/Queries/useClub'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; import { Award, ClubDetail, FAQ, IdealCandidate } from '@/types/club'; import * as Styled from './ClubIntroEditTab.styles'; diff --git a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts index f1e2e0e44..40ff7ce80 100644 --- a/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts @@ -1,5 +1,5 @@ -import { colors } from '@/styles/theme/colors'; import styled from 'styled-components'; +import { colors } from '@/styles/theme/colors'; export const Container = styled.div` display: flex; @@ -24,8 +24,10 @@ export const AlwaysRecruitButton = styled.button<{ $active: boolean }>` flex-shrink: 0; color: ${({ $active }) => ($active ? colors.base.white : colors.gray[700])}; - background: ${({ $active }) => ($active ? colors.primary[800] : colors.gray[300])}; - border: ${({ $active }) => ($active ? 'none' : `1px solid ${colors.gray[500]}`)}; + background: ${({ $active }) => + $active ? colors.primary[800] : colors.gray[300]}; + border: ${({ $active }) => + $active ? 'none' : `1px solid ${colors.gray[500]}`}; transition: background-color 0.12s ease, transform 0.06s ease; diff --git a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx index d0336073f..d829969a0 100644 --- a/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx +++ b/frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx @@ -4,17 +4,17 @@ import { applyToClub } from '@/apis/application'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetApplication } from '@/hooks/Queries/useApplication'; -import { useGetClubDetail } from '@/hooks/Queries/useClub'; import { useAnswers } from '@/hooks/Application/useAnswers'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; -import { validateAnswers } from '@/utils/useValidateAnswers'; +import { useGetApplication } from '@/hooks/Queries/useApplication'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; import { PageContainer } from '@/styles/PageContainer.styles'; import { Question } from '@/types/application'; import { parseDescriptionWithLinks } from '@/utils/parseDescriptionWithLinks'; +import { validateAnswers } from '@/utils/useValidateAnswers'; import * as Styled from './ApplicationFormPage.styles'; const ApplicationFormPage = () => { diff --git a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx index d21996a2a..12951df08 100644 --- a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx +++ b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx @@ -3,10 +3,10 @@ import { useParams, useSearchParams } from 'react-router-dom'; import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import { PAGE_VIEW, USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/Queries/useClub'; -import useDevice from '@/hooks/useDevice'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; +import useDevice from '@/hooks/useDevice'; import ClubFeed from '@/pages/ClubDetailPage/components/ClubFeed/ClubFeed'; import ClubIntroContent from '@/pages/ClubDetailPage/components/ClubIntroContent/ClubIntroContent'; import ClubProfileCard from '@/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard'; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts index 9c66b8d9b..11e75c495 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.styles.ts @@ -17,9 +17,10 @@ export const ApplyButton = styled.button` justify-content: center; border: none; border-radius: 10px; - cursor: ${({disabled}) => (disabled ? 'default' : 'pointer')}; + cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')}; transition: transform 0.2s ease-in-out; - background-color: ${({ disabled }) => disabled ? colors.gray[500] : colors.primary[800]}; + background-color: ${({ disabled }) => + disabled ? colors.gray[500] : colors.primary[800]}; padding: 10px 40px; width: 517px; @@ -40,7 +41,8 @@ export const ApplyButton = styled.button` height: 44px; font-size: 16px; font-weight: 500; - background-color: ${({ disabled }) => disabled ? colors.gray[500] : colors.gray[900]}; + background-color: ${({ disabled }) => + disabled ? colors.gray[500] : colors.gray[900]}; } `; @@ -49,4 +51,4 @@ export const Separator = styled.span` border-left: 1px solid #787878; height: 12px; display: inline-block; -`; \ No newline at end of file +`; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx index 47dfbbb5a..e3a5a52d5 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx @@ -1,13 +1,13 @@ -import * as Styled from './ClubApplyButton.styles'; +import { useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { useGetClubDetail } from '@/hooks/Queries/useClub'; import { getApplication, getApplicationOptions } from '@/apis/application'; -import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import ApplicationSelectModal from '@/components/application/modals/ApplicationSelectModal'; import { USER_EVENT } from '@/constants/eventName'; -import { useState } from 'react'; +import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import { ApplicationForm, ApplicationFormMode } from '@/types/application'; -import ApplicationSelectModal from '@/components/application/modals/ApplicationSelectModal'; import ShareButton from '../ShareButton/ShareButton'; +import * as Styled from './ClubApplyButton.styles'; interface ClubApplyButtonProps { deadlineText?: string; @@ -20,7 +20,9 @@ const ClubApplyButton = ({ deadlineText }: ClubApplyButtonProps) => { const { data: clubDetail } = useGetClubDetail(clubId!); const [isApplicationModalOpen, setIsApplicationModalOpen] = useState(false); - const [applicationOptions, setApplicationOptions] = useState([]); + const [applicationOptions, setApplicationOptions] = useState< + ApplicationForm[] + >([]); if (!clubId || !clubDetail) return null; @@ -104,9 +106,10 @@ const ClubApplyButton = ({ deadlineText }: ClubApplyButtonProps) => { return ( - + onClick={handleApplyButtonClick} + > {renderButtonContent()} { ); }; -export default ClubApplyButton; \ No newline at end of file +export default ClubApplyButton; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts index f32bdfd06..478b04549 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts +++ b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts @@ -1,5 +1,5 @@ -import { media } from '@/styles/mediaQuery'; import styled from 'styled-components'; +import { media } from '@/styles/mediaQuery'; export const ClubDetailFooterContainer = styled.div` position: sticky; @@ -18,4 +18,4 @@ export const ClubDetailFooterContainer = styled.div` ${media.mobile} { padding: 10px 0px 16px 0px; } -`; \ No newline at end of file +`; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx index 52b4c3184..952ea0713 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx @@ -1,8 +1,8 @@ +import { RecruitmentStatus } from '@/types/club'; import getDeadlineText from '@/utils/getDeadLineText'; import { recruitmentDateParser } from '@/utils/recruitmentDateParser'; import ClubApplyButton from '../ClubApplyButton/ClubApplyButton'; import * as Styled from './ClubDetailFooter.styles'; -import { RecruitmentStatus } from '@/types/club'; interface ClubDetailFooterProps { recruitmentStart: string; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts b/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts index 330de0e5e..56d496943 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts +++ b/frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts @@ -163,7 +163,7 @@ export const SocialText = styled.span` `; export const SocialUrl = styled.span` - color: #009CF6; + color: #009cf6; display: inline-block; max-width: 180px; overflow: hidden; @@ -174,11 +174,11 @@ export const SocialUrl = styled.span` ${media.laptop} { max-width: 100px; } - + ${media.tablet} { max-width: 180px; } - + ${media.mobile} { max-width: 120px; } diff --git a/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts b/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts index 83be324a1..b7b30f3c6 100644 --- a/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts +++ b/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts @@ -200,7 +200,8 @@ export const ThumbnailList = styled.div` `; export const Thumbnail = styled.button<{ isActive: boolean }>` - border: 2px solid ${({ isActive }) => (isActive ? colors.primary[900] : 'transparent')}; + border: 2px solid + ${({ isActive }) => (isActive ? colors.primary[900] : 'transparent')}; border-radius: 6px; padding: 0; background: none; @@ -212,7 +213,8 @@ export const Thumbnail = styled.button<{ isActive: boolean }>` transition: all 0.2s; &:hover { - border-color: ${({ isActive }) => (isActive ? colors.primary[900] : '#ddd')}; + border-color: ${({ isActive }) => + isActive ? colors.primary[900] : '#ddd'}; } img { diff --git a/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx b/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx index 409a16014..b16a3baab 100644 --- a/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx +++ b/frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx @@ -5,8 +5,8 @@ import { Swiper, SwiperSlide } from 'swiper/react'; import 'swiper/css/navigation'; import NextButton from '@/assets/images/icons/next_button_icon.svg'; import PrevButton from '@/assets/images/icons/prev_button_icon.svg'; -import * as Styled from './PhotoModal.styles'; import PortalModal from '@/components/common/Modal/PortalModal'; +import * as Styled from './PhotoModal.styles'; interface PhotoModalProps { isOpen: boolean; @@ -39,12 +39,8 @@ const PhotoModal = ({ isOpen, onClose, clubName, photos }: PhotoModalProps) => { if (!isOpen) return null; return ( - - e.stopPropagation()}> + + e.stopPropagation()}> {clubName} diff --git a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts index 7f4c66d22..f214210ca 100644 --- a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts +++ b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.styles.ts @@ -1,5 +1,5 @@ -import { media } from '@/styles/mediaQuery'; import styled from 'styled-components'; +import { media } from '@/styles/mediaQuery'; export const ShareButtonContainer = styled.div` display: flex; @@ -15,4 +15,4 @@ export const ShareButtonIcon = styled.img` width: 44px; height: 44px; } -`; \ No newline at end of file +`; diff --git a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx index 1eee406d5..6733e674b 100644 --- a/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ShareButton/ShareButton.tsx @@ -1,8 +1,8 @@ -import ShareIcon from '@/assets/images/icons/share_icon.svg'; import ShareIconMobile from '@/assets/images/icons/share_icon_mobile.svg'; +import ShareIcon from '@/assets/images/icons/share_icon.svg'; import { USER_EVENT } from '@/constants/eventName'; -import { useGetClubDetail } from '@/hooks/Queries/useClub'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import { useGetClubDetail } from '@/hooks/Queries/useClub'; import useDevice from '@/hooks/useDevice'; import * as Styled from './ShareButton.styles'; @@ -57,9 +57,9 @@ const ShareButton = ({ clubId }: ShareButtonProps) => { role='button' aria-label='카카오톡으로 동아리 정보 공유하기' > - ); diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index aa1023278..34e80b390 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -3,8 +3,8 @@ import Footer from '@/components/common/Footer/Footer'; import Header from '@/components/common/Header/Header'; import Spinner from '@/components/common/Spinner/Spinner'; import { PAGE_VIEW } from '@/constants/eventName'; -import { useGetCardList } from '@/hooks/Queries/useClub'; import useTrackPageView from '@/hooks/Mixpanel/useTrackPageView'; +import { useGetCardList } from '@/hooks/Queries/useClub'; import Banner from '@/pages/MainPage/components/Banner/Banner'; import CategoryButtonList from '@/pages/MainPage/components/CategoryButtonList/CategoryButtonList'; import ClubCard from '@/pages/MainPage/components/ClubCard/ClubCard'; diff --git a/frontend/src/pages/MainPage/components/Banner/Banner.tsx b/frontend/src/pages/MainPage/components/Banner/Banner.tsx index fb5c52311..a7ddc0cf4 100644 --- a/frontend/src/pages/MainPage/components/Banner/Banner.tsx +++ b/frontend/src/pages/MainPage/components/Banner/Banner.tsx @@ -5,8 +5,8 @@ import { Swiper, SwiperSlide } from 'swiper/react'; import NextButton from '@/assets/images/icons/next_button_icon.svg'; import PrevButton from '@/assets/images/icons/prev_button_icon.svg'; import { USER_EVENT } from '@/constants/eventName'; -import useDevice from '@/hooks/useDevice'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useDevice from '@/hooks/useDevice'; import useNavigator from '@/hooks/useNavigator'; import { detectPlatform, getAppStoreLink } from '@/utils/appStoreLink'; import * as Styled from './Banner.styles'; diff --git a/frontend/src/pages/MainPage/components/Popup/Popup.tsx b/frontend/src/pages/MainPage/components/Popup/Popup.tsx index 1f7a99000..8354ebee8 100644 --- a/frontend/src/pages/MainPage/components/Popup/Popup.tsx +++ b/frontend/src/pages/MainPage/components/Popup/Popup.tsx @@ -1,8 +1,8 @@ import { MouseEvent, useEffect, useState } from 'react'; import AppDownloadImage from '@/assets/images/popup/app-download.svg'; import { USER_EVENT } from '@/constants/eventName'; -import useDevice from '@/hooks/useDevice'; import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack'; +import useDevice from '@/hooks/useDevice'; import { detectPlatform, getAppStoreLink } from '@/utils/appStoreLink'; import * as Styled from './Popup.styles'; diff --git a/frontend/src/utils/getDeadLineText.test.ts b/frontend/src/utils/getDeadLineText.test.ts index 14619aa2c..67cbd0b15 100644 --- a/frontend/src/utils/getDeadLineText.test.ts +++ b/frontend/src/utils/getDeadLineText.test.ts @@ -3,80 +3,79 @@ import getDeadlineText from './getDeadLineText'; describe('getDeadlineText 함수 테스트', () => { it.each([ [ - '오늘이 모집 종료일인 경우', + '오늘이 모집 종료일인 경우', new Date('2025-04-01'), new Date('2025-04-10'), - '2025-04-10', - 'OPEN', - 'D-Day' + '2025-04-10', + 'OPEN', + 'D-Day', ], [ - '모집 종료일까지 5일 남은 경우', + '모집 종료일까지 5일 남은 경우', new Date('2025-04-01'), new Date('2025-04-10'), '2025-04-05', 'OPEN', - 'D-5' + 'D-5', ], [ - '오늘이 모집 종료일 이후인 경우', + '오늘이 모집 종료일 이후인 경우', new Date('2025-04-01'), new Date('2025-04-10'), '2025-04-11', - 'CLOSED', - '모집 마감' + 'CLOSED', + '모집 마감', ], [ - '모집 시작일이 아직 남은 경우 (시간 포함)', + '모집 시작일이 아직 남은 경우 (시간 포함)', new Date('2025-04-01T09:00:00'), new Date('2025-04-10'), - '2025-03-30', - 'UPCOMING', - '4월 1일 09:00 모집 시작' + '2025-03-30', + 'UPCOMING', + '4월 1일 09:00 모집 시작', ], [ - '모집 시작 시간이 00:00인 경우', + '모집 시작 시간이 00:00인 경우', new Date('2025-04-01T00:00:00'), new Date('2025-04-10'), - '2025-03-30', - 'UPCOMING', - '4월 1일 모집 시작' + '2025-03-30', + 'UPCOMING', + '4월 1일 모집 시작', ], - - ])('%s', + ])( + '%s', ( - _, - recruitmentStart, - recruitmentEnd, - todayStr, - recruitmentStatus, + _, + recruitmentStart, + recruitmentEnd, + todayStr, + recruitmentStatus, expected, ) => { - const today = new Date(todayStr); - expect( - getDeadlineText( - recruitmentStart, - recruitmentEnd, - recruitmentStatus, - today - ) - ).toBe( - expected); - }); + const today = new Date(todayStr); + expect( + getDeadlineText( + recruitmentStart, + recruitmentEnd, + recruitmentStatus, + today, + ), + ).toBe(expected); + }, + ); it('모집 기간이 null인 경우 모집 마감을 반환해야 한다', () => { - expect( - getDeadlineText(null, null, 'CLOSED')).toBe('모집 마감'); + expect(getDeadlineText(null, null, 'CLOSED')).toBe('모집 마감'); }); - + it('모집 중 상태인데 모집 종료일까지 1년 이상 남으면 상시 모집을 반환해야 한다', () => { - expect( - getDeadlineText( - new Date('2025-01-01'), - new Date('2027-01-01'), - 'OPEN', - new Date('2025-01-01'), - ), - ).toBe('상시 모집'); -}); + expect( + getDeadlineText( + new Date('2025-01-01'), + new Date('2027-01-01'), + 'OPEN', + new Date('2025-01-01'), + ), + ).toBe('상시 모집'); + }); }); diff --git a/frontend/src/utils/getDeadLineText.ts b/frontend/src/utils/getDeadLineText.ts index bf8b66c89..764f8d8b7 100644 --- a/frontend/src/utils/getDeadLineText.ts +++ b/frontend/src/utils/getDeadLineText.ts @@ -1,4 +1,4 @@ -import { format, differenceInCalendarDays } from 'date-fns'; +import { differenceInCalendarDays, format } from 'date-fns'; import { ko } from 'date-fns/locale'; const RECRUITMENT_STATUS = { @@ -22,17 +22,14 @@ const getDeadlineText = ( const hour = recruitmentStart.getHours(); const minute = recruitmentStart.getMinutes(); - let formatStr = - hour === 0 && minute === 0 - ? 'M월 d일' - : 'M월 d일 HH:mm'; + let formatStr = hour === 0 && minute === 0 ? 'M월 d일' : 'M월 d일 HH:mm'; return `${format(recruitmentStart, formatStr, { locale: ko })} ${RECRUITMENT_STATUS.UPCOMING}`; } if (!recruitmentEnd) return RECRUITMENT_STATUS.CLOSED; const days = differenceInCalendarDays(recruitmentEnd, today); - if (days > 365) return RECRUITMENT_STATUS.ALWAYS; // D-day가 의미 없을 정도로 긴 경우 '상시 모집'으로 표시 + if (days > 365) return RECRUITMENT_STATUS.ALWAYS; // D-day가 의미 없을 정도로 긴 경우 '상시 모집'으로 표시 return days > 0 ? `D-${days}` : 'D-Day'; }; From cf4e6a86c999a5cbdb365ed5f81de4c6bea8a91f Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:33:12 +0900 Subject: [PATCH 11/19] =?UTF-8?q?fix:=20useGetApplication=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=20=ED=82=A4=20=EC=B6=A9=EB=8F=8C=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - clubId/applicationFormId가 없을 때 queryKeys.application.all 대신 고유한 키 사용 - useGetApplicationlist와 캐시 키 충돌로 인한 타입 불일치 문제 해결 - enabled가 false일 때도 명확한 캐시 분리 보장 --- frontend/src/hooks/Queries/useApplication.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/hooks/Queries/useApplication.ts b/frontend/src/hooks/Queries/useApplication.ts index 53607fec4..1c3848b4f 100644 --- a/frontend/src/hooks/Queries/useApplication.ts +++ b/frontend/src/hooks/Queries/useApplication.ts @@ -13,10 +13,10 @@ export const useGetApplication = ( applicationFormId: string | undefined, ) => { return useQuery({ - queryKey: - clubId && applicationFormId - ? queryKeys.application.detail(clubId, applicationFormId) - : queryKeys.application.all, + queryKey: queryKeys.application.detail( + clubId || 'unknown', + applicationFormId || 'unknown', + ), queryFn: () => getApplication(clubId!, applicationFormId!), enabled: !!clubId && !!applicationFormId, }); From 3099a99e956e0dd13d153cfe988c391b167ef979 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:40:09 +0900 Subject: [PATCH 12/19] =?UTF-8?q?fix:=20ClubDetailPage=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=B4=ED=81=AC=20=EC=88=9C=EC=84=9C=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 - error 체크를 clubDetail 체크보다 먼저 수행 - API 에러 발생 시 에러 메시지가 정상적으로 표시되도록 개선 --- frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx index 12951df08..5cfb17880 100644 --- a/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx +++ b/frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx @@ -48,14 +48,14 @@ const ClubDetailPage = () => { trackEvent(USER_EVENT.CLUB_FEED_TAB_CLICKED); }, [setSearchParams, trackEvent]); - if (!clubDetail) { - return null; - } - if (error) { return
에러가 발생했습니다.
; } + if (!clubDetail) { + return null; + } + return ( <> {(isLaptop || isDesktop) &&
} From dfe4b648df05410147905b09f9973ccdae427b36 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:40:22 +0900 Subject: [PATCH 13/19] =?UTF-8?q?refactor:=20ClubSearchResponse=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=84=20club.ts=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - club.responses.ts 파일 삭제 - ClubSearchResponse를 club.ts로 이동 - 단일 사용처만 있는 타입의 불필요한 파일 분리 제거 --- frontend/src/hooks/Queries/useClub.ts | 3 +-- frontend/src/types/club.responses.ts | 6 ------ frontend/src/types/club.ts | 5 +++++ 3 files changed, 6 insertions(+), 8 deletions(-) delete mode 100644 frontend/src/types/club.responses.ts diff --git a/frontend/src/hooks/Queries/useClub.ts b/frontend/src/hooks/Queries/useClub.ts index d19449f67..bd64cb31f 100644 --- a/frontend/src/hooks/Queries/useClub.ts +++ b/frontend/src/hooks/Queries/useClub.ts @@ -11,8 +11,7 @@ import { updateClubDetail, } from '@/apis/club'; import { queryKeys } from '@/constants/queryKeys'; -import { ClubDescription, ClubDetail } from '@/types/club'; -import { ClubSearchResponse } from '@/types/club.responses'; +import { ClubDescription, ClubDetail, ClubSearchResponse } from '@/types/club'; import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; diff --git a/frontend/src/types/club.responses.ts b/frontend/src/types/club.responses.ts deleted file mode 100644 index a2603c4ff..000000000 --- a/frontend/src/types/club.responses.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Club } from './club'; - -export interface ClubSearchResponse { - clubs: Club[]; - totalCount: number; -} diff --git a/frontend/src/types/club.ts b/frontend/src/types/club.ts index 37f1e69b8..d48f82cff 100644 --- a/frontend/src/types/club.ts +++ b/frontend/src/types/club.ts @@ -82,3 +82,8 @@ export interface ClubApiResponse { category: string; division: string; } + +export interface ClubSearchResponse { + clubs: Club[]; + totalCount: number; +} From 6ae9a31576fea82f1407b83af93df0b759eb29c4 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:42:25 +0900 Subject: [PATCH 14/19] =?UTF-8?q?refactor:=20usePhotoModal=20=ED=9B=85=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=A1=9C=EC=A7=81=20=EC=9D=B8?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ClubFeed 컴포넌트 내부로 모달 상태 관리 로직 이동 - hooks/PhotoList/usePhotoModal.ts 파일 삭제 - 단일 사용처만 있는 불필요한 훅 추상화 제거 --- frontend/src/hooks/PhotoList/usePhotoModal.ts | 14 -------------- .../components/ClubFeed/ClubFeed.tsx | 11 +++++++++-- 2 files changed, 9 insertions(+), 16 deletions(-) delete mode 100644 frontend/src/hooks/PhotoList/usePhotoModal.ts diff --git a/frontend/src/hooks/PhotoList/usePhotoModal.ts b/frontend/src/hooks/PhotoList/usePhotoModal.ts deleted file mode 100644 index 0318e1c84..000000000 --- a/frontend/src/hooks/PhotoList/usePhotoModal.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useState } from 'react'; - -export const usePhotoModal = () => { - const [isOpen, setIsOpen] = useState(false); - const [index, setIndex] = useState(0); - - const open = (i: number) => { - setIndex(i); - setIsOpen(true); - }; - const close = () => setIsOpen(false); - - return { isOpen, index, open, close, setIndex }; -}; diff --git a/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx b/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx index 65e904a8c..8f6e97fa0 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx @@ -1,4 +1,4 @@ -import { usePhotoModal } from '@/hooks/PhotoList/usePhotoModal'; +import { useState } from 'react'; import PhotoModal from '@/pages/ClubDetailPage/components/PhotoModal/PhotoModal'; import * as Styled from './ClubFeed.styles'; @@ -8,7 +8,14 @@ interface Props { } const ClubFeed = ({ feed, clubName = '동아리' }: Props) => { - const { isOpen, index, open, close, setIndex } = usePhotoModal(); + const [isOpen, setIsOpen] = useState(false); + const [index, setIndex] = useState(0); + + const open = (i: number) => { + setIndex(i); + setIsOpen(true); + }; + const close = () => setIsOpen(false); if (!feed || feed.length === 0) { return ( From 91eac2fa1d7515af7e9a0fe11caf8189617cc9b6 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 00:55:30 +0900 Subject: [PATCH 15/19] =?UTF-8?q?fix:=20ClubFeed=20=ED=94=BC=EB=93=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=8B=9C=20index=20=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EB=B3=B4=EC=9E=A5=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feed 배열 변경 시 index가 범위를 벗어나지 않도록 useEffect 추가 - 빈 배열일 때 모달 자동 닫기 및 index 초기화 - PhotoModal에서 잘못된 배열 접근 방지 --- .../ClubDetailPage/components/ClubFeed/ClubFeed.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx b/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx index 8f6e97fa0..3dc1c0ea8 100644 --- a/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx +++ b/frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import PhotoModal from '@/pages/ClubDetailPage/components/PhotoModal/PhotoModal'; import * as Styled from './ClubFeed.styles'; @@ -17,6 +17,15 @@ const ClubFeed = ({ feed, clubName = '동아리' }: Props) => { }; const close = () => setIsOpen(false); + useEffect(() => { + if (!feed || feed.length === 0) { + setIsOpen(false); + setIndex(0); + } else if (index >= feed.length) { + setIndex(feed.length - 1); + } + }, [feed, index]); + if (!feed || feed.length === 0) { return ( From 7396eb80f65e23b0a1f0bd61015e3e7ed98058f5 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 01:31:44 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor:=20PhotoLayout=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/photoLayout.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 frontend/src/constants/photoLayout.ts diff --git a/frontend/src/constants/photoLayout.ts b/frontend/src/constants/photoLayout.ts deleted file mode 100644 index 3a6ba335f..000000000 --- a/frontend/src/constants/photoLayout.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const CARD_GAP = 20; -export const DESKTOP_CARD_CONTENT_WIDTH = 400; -export const MOBILE_CARD_CONTENT_WIDTH = 350; - -export const DESKTOP_CARD_WIDTH = DESKTOP_CARD_CONTENT_WIDTH + CARD_GAP; -export const MOBILE_CARD_WIDTH = MOBILE_CARD_CONTENT_WIDTH + CARD_GAP; From 146de1b75b84877796107b4a5bcde03d62a30918 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 01:32:03 +0900 Subject: [PATCH 17/19] =?UTF-8?q?refactor:=20scrollSection=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/scrollSections.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 frontend/src/constants/scrollSections.ts diff --git a/frontend/src/constants/scrollSections.ts b/frontend/src/constants/scrollSections.ts deleted file mode 100644 index a00e8eb4e..000000000 --- a/frontend/src/constants/scrollSections.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum INFOTABS_SCROLL_INDEX { - INTRODUCE_INFO_TAB = 0, - CLUB_INFO_TAB = 1, - DESCRIPTION_TAB = 2, - PHOTO_LIST_TAB = 3, -} From 390618d09d4af2982780a4e912a659e09e4b240d Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 01:46:28 +0900 Subject: [PATCH 18/19] =?UTF-8?q?refactor:=20netlify=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/netlify.toml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 frontend/netlify.toml diff --git a/frontend/netlify.toml b/frontend/netlify.toml deleted file mode 100644 index 67560159c..000000000 --- a/frontend/netlify.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[redirects]] -from = "/*" -to = "/index.html" -status = 200 \ No newline at end of file From 4636956715211786feb4834cd76fb282d156a458 Mon Sep 17 00:00:00 2001 From: seongwon seo Date: Sun, 18 Jan 2026 12:12:18 +0900 Subject: [PATCH 19/19] =?UTF-8?q?refactor:=20useGetApplicationList?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/Queries/useApplication.ts | 2 +- .../ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx | 4 ++-- .../AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/hooks/Queries/useApplication.ts b/frontend/src/hooks/Queries/useApplication.ts index 1c3848b4f..d49317f1f 100644 --- a/frontend/src/hooks/Queries/useApplication.ts +++ b/frontend/src/hooks/Queries/useApplication.ts @@ -22,7 +22,7 @@ export const useGetApplication = ( }); }; -export const useGetApplicationlist = () => { +export const useGetApplicationList = () => { return useQuery({ queryKey: queryKeys.application.all, queryFn: () => getAllApplicationForms(), diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx index c65ff8bae..afd5a7b17 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx @@ -9,7 +9,7 @@ import Spinner from '@/components/common/Spinner/Spinner'; import { useDeleteApplication, useDuplicateApplication, - useGetApplicationlist, + useGetApplicationList, useUpdateApplicationStatus, } from '@/hooks/Queries/useApplication'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; @@ -20,7 +20,7 @@ import { ApplicationFormItem, SemesterGroup } from '@/types/application'; const MAX_INITIAL_ITEMS = 3; const ApplicationListTab = () => { - const { data: allforms, isLoading, isError, error } = useGetApplicationlist(); + const { data: allforms, isLoading, isError, error } = useGetApplicationList(); const { mutate: deleteApplication } = useDeleteApplication(); const { mutate: duplicateApplication } = useDuplicateApplication(); const { mutate: updateStatus } = useUpdateApplicationStatus(); diff --git a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx index 9860fb773..a7b7e0730 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx @@ -9,7 +9,7 @@ import Spinner from '@/components/common/Spinner/Spinner'; import { useDeleteApplication, useDuplicateApplication, - useGetApplicationlist, + useGetApplicationList, } from '@/hooks/Queries/useApplication'; import ApplicationRowItem from '@/pages/AdminPage/components/ApplicationRow/ApplicationRowItem'; import { ContentSection } from '@/pages/AdminPage/components/ContentSection/ContentSection'; @@ -21,7 +21,7 @@ const MAX_INITIAL_ITEMS = 3; const ApplicationListTab = () => { const queryClient = useQueryClient(); const navigate = useNavigate(); - const { data: allforms, isLoading, isError, error } = useGetApplicationlist(); + const { data: allforms, isLoading, isError, error } = useGetApplicationList(); const { mutate: deleteApplication } = useDeleteApplication(); const { mutate: duplicateApplication } = useDuplicateApplication();