Conversation
- Mixpanel 관련 훅을 hooks/Mixpanel/ 폴더로 이동 - Scroll 관련 훅을 hooks/Scroll/ 폴더로 이동 - Application 관련 훅을 hooks/Application/ 폴더로 이동 - useValidateAnswers는 순수 함수이므로 utils로 이동 - 모든 import 경로 업데이트
…(applicants, application, auth, club, image) - apis/utils/apiHelpers.ts 추가하여 공통 에러 처리 로직 분리 - handleResponse와 withErrorHandling으로 중복 코드 제거 - 커스텀 에러 메시지 선택적 지원으로 기존 동작 유지 - 모든 API에 일관된 에러 처리 패턴 적용 - 43개 파일의 import 경로 업데이트 - 24개 API 파일을 5개 도메인별 파일로 통합 (applicants, application, auth, club, image) - apis/utils/apiHelpers.ts 추가하여 공통 에러 처리 로직 분리 - handleResponse와 withErrorHandling으로 중복 코드 제거 - 커스텀 에러 메시지 선택적 지원으로 기존 동작 유지 - 모든 API에 일관된 에러 처리 패턴 적용 - 43개 파일의 import 경로 업데이트
- API 함수에 사용자 친화적인 한국어 에러 메시지 추가 - queryKeys를 constants/queryKeys.ts로 분리 및 factory 패턴 적용 - hooks/queries → hooks/Queries 폴더 구조 정리
- apiHelpers/handleResponse: 빈 응답, JSON 파싱 실패, 잘못된 Content-Type에 대한 예외 처리 강화 - auth/getClubIdByToken: 응답 데이터 내 clubId 유효성 검사 로직 추가 - club/getClubDetail: 응답 데이터 내 club 객체 유효성 검사 로직 추가 - club/getClubList: 리스트 데이터 Null 체크 및 기본값(Fallback) 처리 추가
- App.tsx: QueryClient 전역 설정 추가 (staleTime: 60s, retry: 1) - Hooks: - 개별 훅 내 중복된 retry, staleTime 옵션 제거 - invalidateQueries 로직을 컴포넌트에서 훅 내부 onSuccess로 이동하여 응집도 향상 - 훅 내부 alert 제거 및 console.error로 로깅 전환 (SoC 준수) - Components: - mutate 호출 시 onError/onSuccess 콜백을 통해 사용자 알림(alert) 처리하도록 수정
- Hooks: useUpdateApplicationStatus 훅 추가 및 useDuplicateApplication 적용 - ApplicationListTab: 상태 변경 로직을 훅으로 위임하고 누락된 onDuplicate 핸들러 연결 - ApplicantDetailPage: 지원자 정보 수정 시 에러 핸들링(onError) 추가 - ApplicantsTab: 코드 정리 및 라우팅 관련 수정
- APPLICATION_FORM.ts → applicationForm.ts - CLUB_UNION_INFO.ts → clubUnionInfo.ts - INITIAL_FORM_DATA.ts → initialFormData.ts - 관련 import 경로 9개 파일 업데이트
- clubId/applicationFormId가 없을 때 queryKeys.application.all 대신 고유한 키 사용 - useGetApplicationlist와 캐시 키 충돌로 인한 타입 불일치 문제 해결 - enabled가 false일 때도 명확한 캐시 분리 보장
- error 체크를 clubDetail 체크보다 먼저 수행 - API 에러 발생 시 에러 메시지가 정상적으로 표시되도록 개선
- club.responses.ts 파일 삭제 - ClubSearchResponse를 club.ts로 이동 - 단일 사용처만 있는 타입의 불필요한 파일 분리 제거
- ClubFeed 컴포넌트 내부로 모달 상태 관리 로직 이동 - hooks/PhotoList/usePhotoModal.ts 파일 삭제 - 단일 사용처만 있는 불필요한 훅 추상화 제거
- feed 배열 변경 시 index가 범위를 벗어나지 않도록 useEffect 추가 - 빈 배열일 때 모달 자동 닫기 및 index 초기화 - PhotoModal에서 잘못된 배열 접근 방지
…OA-530 [feature] 앱 다운로드 배너 트래킹에 A/B 테스트 그룹 정보 추가
…ing-MOA-532 [refacotor] Tanstack Query 리팩토링 및 API/Hooks 구조 개선
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (3)
Warning
|
| Cohort / File(s) | 변경 사항 요약 |
|---|---|
통합된 API 모듈 frontend/src/apis/auth.ts, frontend/src/apis/applicants.ts, frontend/src/apis/application.ts, frontend/src/apis/club.ts, frontend/src/apis/image.ts |
여러 개별 엔드포인트 파일을 단일 도메인 파일로 병합하여 관련 함수들을 내보냄 (인증, 지원자, 지원서 CRUD·상태관리, 클럽, 이미지 업로드 등). |
삭제된 개별 API 파일 frontend/src/apis/auth/*, frontend/src/apis/application/*, frontend/src/apis/applicants/*, frontend/src/apis/image/*, frontend/src/apis/getClubDetail.ts, frontend/src/apis/getClubList.ts, frontend/src/apis/updateClub*.ts |
기존 per-endpoint 파일들 제거(기능이 통합 모듈로 이동). |
API 유틸리티 추가 frontend/src/apis/utils/apiHelpers.ts, frontend/src/constants/queryKeys.ts |
handleResponse와 withErrorHandling 도입 및 React Query용 중앙화된 queryKeys 추가. |
훅 재배치/통합 (새 경로) frontend/src/hooks/Queries/* (예: useApplicants.ts, useApplication.ts, useClub.ts, useClubImages.ts, useClubCover.ts) |
기존 hooks/queries/* 파일들을 통합·이관하고 훅들을 재구성(쿼리 무효화/선택자 포함). |
삭제된 훅/기능 훅 파일들 frontend/src/hooks/queries/*, frontend/src/hooks/PhotoList/*, frontend/src/hooks/PhotoModal/*, frontend/src/hooks/InfoTabs/useAutoScroll.ts |
여러 개별 훅 파일 삭제(동일 기능이 통합 모듈로 대체되었거나 로컬 상태로 전환). |
Mixpanel · Scroll 훅 경로 변경 frontend/src/hooks/Mixpanel/*, frontend/src/hooks/Scroll/* |
Mixpanel 및 Scroll 관련 훅을 전용 디렉토리로 이동하고 다수 import 경로 갱신. |
App 및 QueryClient 설정 변경 frontend/src/App.tsx |
QueryClient 기본 옵션 변경 (staleTime=60s, queries.retry=1, mutations.retry=0) 및 ScrollToTop import 경로 조정. |
Netlify 설정 제거 frontend/netlify.toml |
SPA catch-all 리다이렉트 규칙 제거. |
이미지 업로드 API 통합 frontend/src/apis/image.ts |
presigned 업로드→storage PUT→완료 호출 흐름을 통합한 API 제공 (cover/feed/logo), 업로드 헬퍼 포함. |
컴포넌트·스토리북·스타일 import 경로 일괄 업데이트 frontend/src/components/**, frontend/src/pages/**, frontend/.storybook/preview.ts, frontend/.prettierrc, frontend/config/vite.config.ts |
대량의 import 경로 변경(상수/훅/대소문자 표준화), Storybook meta/argTypes 확장, 포맷 정리. |
타입·상수 변경 frontend/src/types/club.ts, frontend/src/constants/* |
ClubSearchResponse 타입 추가/이동, photoLayout 상수 삭제, APPLICATION_FORM → applicationForm 경로 표준화. |
Sequence Diagram(s)
sequenceDiagram
participant UI as Client(UI)
participant API as Frontend API module (image.ts)
participant Storage as Presigned Storage (S3/GCS)
participant Query as React Query (cache)
rect rgba(220,240,255,0.5)
UI->>API: coverApi.getUploadUrl(clubId, fileName, contentType)
API-->>UI: PresignedData{ presignedUrl, finalUrl, clubId }
end
rect rgba(220,255,220,0.5)
UI->>Storage: PUT presignedUrl (file)
Storage-->>UI: 200/OK
end
rect rgba(255,240,220,0.5)
UI->>API: coverApi.completeUpload(clubId, finalUrl)
API-->>UI: 200/ok
API->>Query: invalidate queryKeys.club.detail(clubId)
Query-->>UI: refetch/update UI
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
- [refacotor] Tanstack Query 리팩토링 및 API/Hooks 구조 개선 #1058: 동일한 tanstack-query·API·훅 통합 리팩토링 — QueryClient 설정, APIs 통합 및 apiHelpers 추가와 코드 중복·교차 영향 가능.
- [feature] 다중 지원서 API 연결 #783: applicants API·훅 재작업 — getClubApplicants / deleteApplicants 및 applicants 훅/관리 UI 변경과 강한 연관성.
- [release] FE v1.1.7 #918: application API 헬퍼 통합/재배치 관련 — application 엔드포인트 재배치 및 훅 변경과 직접적 관련.
Suggested reviewers
- lepitaaar
- oesnuj
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | 제목 '[release] FE v1.1.17'은 릴리스 버전을 명확히 명시하고 있어 주요 변경사항의 의도를 잘 전달합니다. |
| Docstring Coverage | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
frontend/src/pages/AdminPage/components/QuestionBuilder/QuestionBuilder.tsx (1)
30-36: React Hooks 규칙 위반: 조건부 반환 이후에 훅이 호출됨Line 30-32의 early return이 Line 34-38의
useState와 Line 40-46의useEffect호출 전에 위치해 있습니다. 이는 React의 Hooks 규칙을 위반하여type이QUESTION_LABEL_MAP에 없을 때 훅 호출 순서가 달라지면서 런타임 오류가 발생할 수 있습니다.🐛 훅을 조건부 반환 전으로 이동하는 수정 제안
const QuestionBuilder = ({ id, title, description, options, items, type, readOnly, onTitleChange, onItemsChange, onDescriptionChange, onTypeChange, onRequiredChange, onRemoveQuestion, }: QuestionBuilderProps) => { - if (!(type in QUESTION_LABEL_MAP)) { - return null; - } - const [selectionType, setSelectionType] = useState<'single' | 'multi'>( type === 'MULTI_CHOICE' ? 'multi' : 'single', ); const [isDropdownOpen, setIsDropdownOpen] = useState(false); useEffect(() => { if (type === 'MULTI_CHOICE') { setSelectionType('multi'); } else if (type === 'CHOICE') { setSelectionType('single'); } }, [type]); + if (!(type in QUESTION_LABEL_MAP)) { + return null; + } + const renderFieldByQuestionType = () => {frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
67-95: 지원자 ID 누락 시 업데이트 요청 방지 필요
questionId가 라우트 파라미터라 undefined 가능성이 있어, 업데이트 전에 가드가 있으면 안전합니다.✅ 제안 수정
debounce((memo, status) => { function isApplicationStatus(v: unknown): v is ApplicationStatus { return ( typeof v === 'string' && Object.values(ApplicationStatus).includes(v as ApplicationStatus) ); } if (typeof memo !== 'string') return; if (!isApplicationStatus(status)) return; + if (!questionId) return; updateApplicant( [ { memo, status, applicantId: questionId, }, ],frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (1)
63-68: 지원서 옵션 0개일 때 사용자 피드백 필요
현재는 아무 반응 없이 종료되어 UX가 끊길 수 있습니다. 간단한 안내를 추가하면 좋습니다.✅ 제안 수정
const forms = await getApplicationOptions(clubId); if (forms.length <= 0) { + alert('현재 지원 가능한 지원서가 없습니다.'); return; }
🤖 Fix all issues with AI agents
In `@frontend/src/apis/application.ts`:
- Around line 152-171: The updateApplicationStatus function is converting
ApplicationFormItem.status (uppercase strings) into a boolean and sending {
active: newStatus }, which mismatches ApplicationFormData.active (lowercase
string union); confirm the backend contract and then either (A) if the backend
expects a boolean, change the ApplicationFormData.active type to boolean across
types/interfaces, or (B) if the backend expects the lowercase status string,
update updateApplicationStatus to map currentStatus
('ACTIVE'|'PUBLISHED'|'UNPUBLISHED') to the corresponding lowercase value
('active'|'published'|'unpublished') and send that string in the request body
(ensure mapping logic is placed in updateApplicationStatus and that
ApplicationFormData.active stays the lowercase union).
In `@frontend/src/apis/auth.ts`:
- Around line 31-46: The logout function currently skips the server logout when
localStorage has no accessToken and doesn't clear the token after a successful
server logout; update logout (and its withErrorHandling wrapper usage) so the
server call to `${API_BASE_URL}/auth/user/logout` is always attempted (do not
early-return when accessToken is missing) and ensure the accessToken is removed
from localStorage in a finally block after the fetch/handleResponse completes or
errors; keep credentials: 'include' for cookie clearing and still propagate
errors via withErrorHandling while guaranteeing
localStorage.removeItem('accessToken') runs.
In `@frontend/src/apis/utils/apiHelpers.ts`:
- Around line 1-38: handleResponse currently returns only result.data which
drops unwrapped JSON responses (e.g. POST /apply); modify the JSON
parsing/fallback so that after parsing (in the try block that sets const result
= JSON.parse(text)) you return result.data if it exists (result && result.data
!== undefined), otherwise return the entire parsed result (and for
non-object/primitives just return result) so both wrapped ({ data: ... }) and
unwrapped responses are preserved; keep the existing error handling and
content-type/content-length checks in handleResponse.
In `@frontend/src/components/common/CustomDropDown/CustomDropDown.stories.tsx`:
- Around line 81-104: The story uses the hardcoded OPTIONS constant when
computing selectedLabel and rendering items, so Storybook controls
(args.options) don't affect the UI; update selectedLabel to derive from
args.options (e.g., find matching label from args.options) and replace the map
over OPTIONS inside the CustomDropDown.Menu with a map over args.options (ensure
type assertion matches the existing cast used for the options prop), adjusting
references to selected and option.value/option.label in CustomDropDown.Item to
use the items from args.options.
In `@frontend/src/hooks/Queries/useClub.ts`:
- Around line 79-81: The onError handler inside the useClub hook currently logs
"Error updating club detail:" which is a typo for the update hook; update the
error message in the onError callback to something accurate (e.g., "Error
updating club details:" or "Error updating club:") within the useClub hook's
update mutation (refer to the onError callback in useClub) so the log reflects
the correct operation and wording.
In `@frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx`:
- Line 24: The cache invalidation uses the wrong query key string so the list
fetched by useGetApplicationList (which relies on queryKeys.application.all)
won't refresh; update the invalidateQueries call to use the same query key used
by useGetApplicationList (queryKeys.application.all) wherever
invalidateQueries({ queryKey: ['applicationForm'] }) is invoked so the cache and
refetch behavior match (search for useGetApplicationList and invalidateQueries
in ApplicationListTab.tsx and replace the mismatched queryKey accordingly).
🧹 Nitpick comments (13)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (1)
165-189: 변경 사항 승인 - 순수 포맷팅 변경입니다.hex 컬러 값의 대소문자 변경(
#009CF6→#009cf6)과 미디어 쿼리 블록 사이의 공백 정리는 기능적 영향이 없습니다. CSS hex 값은 대소문자를 구분하지 않습니다.선택적 개선 제안:
#009cf6하드코딩 대신colors테마 토큰 사용을 고려해 보세요. 파일 내 다른 스타일들과 일관성을 유지하고, 향후 테마 변경 시 유지보수가 용이해집니다.frontend/config/vite.config.ts (1)
2-2: 사용하지 않는 import를 제거하세요.
visualizer를 import했지만 설정에서 사용하지 않고 있습니다. 번들 분석이 필요한 경우 사용하시고, 그렇지 않다면 제거하는 것이 좋습니다.♻️ 제안된 수정
-import { visualizer } from 'rollup-plugin-visualizer';frontend/src/components/common/SearchField/SearchField.stories.tsx (1)
44-116: render 함수 중복을 줄이기 위한 공통 헬퍼 추출을 고려해보세요.세 개의 스토리(Default, WithValue, CustomPlaceholder) 모두 동일한 render 함수를 사용하고 있습니다. 공통 render 함수를 추출하면 코드 중복을 줄일 수 있습니다.
♻️ 공통 render 함수 추출 제안
+const renderSearchField = (args: React.ComponentProps<typeof SearchField>) => { + const [value, setValue] = useState(args.value); + + return ( + <SearchField + {...args} + value={value} + onChange={(newValue) => { + setValue(newValue); + args.onChange(newValue); + }} + /> + ); +}; + export const Default: Story = { args: { value: '', placeholder: '동아리 이름을 입력하세요', autoBlur: true, onChange: () => {}, onSubmit: () => {}, }, - render: (args) => { - const [value, setValue] = useState(args.value); - - return ( - <SearchField - {...args} - value={value} - onChange={(newValue) => { - setValue(newValue); - args.onChange(newValue); - }} - /> - ); - }, + render: renderSearchField, };WithValue와 CustomPlaceholder 스토리에도 동일하게 적용할 수 있습니다.
frontend/src/pages/MainPage/components/Banner/Banner.tsx (1)
8-12: AB 테스트 유틸은 별도 모듈로 분리 고려해 주세요.
컴포넌트 파일에서 유틸을 가져오면 의존성 방향이 모호해질 수 있어,utils/abTest같은 공용 위치로 이동해두면 재사용과 유지보수에 더 유리합니다.frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx (1)
97-97:any타입 사용에 대한 개선 제안
onChangeRaw이벤트 핸들러에서any타입 대신React.SyntheticEvent를 사용하면 타입 안전성을 높일 수 있습니다.♻️ 타입 개선 제안
- onChangeRaw={(e: any) => e.preventDefault()} + onChangeRaw={(e: React.SyntheticEvent) => e.preventDefault()}frontend/src/pages/ClubDetailPage/components/ClubFeed/ClubFeed.tsx (1)
41-42: 변수 섀도잉 발생:index가 상태 변수와 map 콜백 파라미터에서 중복 사용됨.Line 12에서 선언된 상태 변수
index와 Line 41의 map 콜백 파라미터index가 동일한 이름을 사용하고 있습니다. 현재 동작에는 문제가 없지만, 향후 유지보수 시 혼란을 야기할 수 있습니다.♻️ 변수명 변경 제안
- {feed.map((f, index) => ( - <Styled.PhotoItem key={`${f}-${index}`} onClick={() => open(index)}> + {feed.map((f, i) => ( + <Styled.PhotoItem key={`${f}-${i}`} onClick={() => open(i)}> <Styled.PhotoImage src={f} - alt={`활동사진 ${index + 1}`} + alt={`활동사진 ${i + 1}`} loading='lazy' /> </Styled.PhotoItem> ))}frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts (1)
216-217: 디자인 시스템 일관성: 하드코딩된 색상값 대신colors토큰 사용 권장.Line 217에서
#ddd하드코딩 대신 프로젝트의colors테마 토큰을 사용하면 디자인 시스템과의 일관성이 향상됩니다.♻️ 색상 토큰 사용 제안
&:hover { border-color: ${({ isActive }) => - isActive ? colors.primary[900] : '#ddd'}; + isActive ? colors.primary[900] : colors.gray[400]}; }frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx (1)
47-51: 이벤트 트래킹 위치 확인 필요
trackEvent가finally블록 외부에서 호출되어, 만약setLoading(false)이후에 예외가 발생하면 이벤트가 누락될 수 있습니다. "LOGIN_BUTTON_CLICKED" 이벤트가 버튼 클릭 자체를 추적하는 것이라면finally블록 내부나 함수 시작 부분으로 이동하는 것이 더 안정적입니다.♻️ 제안된 수정
} finally { setLoading(false); + trackEvent(ADMIN_EVENT.LOGIN_BUTTON_CLICKED); } - trackEvent(ADMIN_EVENT.LOGIN_BUTTON_CLICKED); };frontend/src/apis/image.ts (2)
10-13: FeedUploadRequest 인터페이스 export 고려
FeedUploadRequest인터페이스가 내부에서만 사용되고 있지만, 이 API를 호출하는 컴포넌트에서 타입 안정성을 위해 해당 인터페이스가 필요할 수 있습니다.♻️ 제안된 수정
-interface FeedUploadRequest { +export interface FeedUploadRequest { fileName: string; contentType: string; }
31-81: coverApi와 logoApi 간 코드 중복
coverApi와logoApi가 거의 동일한 구조(getUploadUrl,completeUpload,delete)를 가지고 있습니다. 현재 상태로도 동작하지만, 향후 유지보수를 위해 팩토리 함수로 추상화하는 것을 고려해볼 수 있습니다.♻️ 팩토리 패턴 예시
const createImageApi = (type: 'cover' | 'logo') => ({ getUploadUrl: async (clubId: string, fileName: string, contentType: string): Promise<PresignedData> => { return withErrorHandling(async () => { const response = await secureFetch( `${API_BASE_URL}/api/club/${clubId}/${type}/upload-url`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fileName, contentType }), }, ); return handleResponse(response, `${type} 업로드 URL 생성 실패 : ${response.status}`); }, `${type} 업로드 URL 생성 중 오류 발생`); }, // ... completeUpload, delete 동일 패턴 }); export const coverApi = createImageApi('cover'); export const logoApi = createImageApi('logo');Also applies to: 121-171
frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx (1)
90-106: 삭제 실패 시 로컬 상태 불일치 가능성 확인 필요
현재는 상태를 먼저 갱신하고 실패 시 알림만 표시합니다. 서버 업데이트가 실패하면 화면과 서버 상태가 불일치할 수 있으니 롤백 또는 재조회가 있는지 확인 부탁드립니다.🔁 롤백을 추가하는 예시
const deleteImage = (index: number) => { if (isLoading) return; - const newList = imageList.filter((_, i) => i !== index); - setImageList(newList); + const prevList = imageList; + const newList = prevList.filter((_, i) => i !== index); + setImageList(newList); updateFeed( { clubId: clubDetail.id, urls: newList, }, { onError: () => { + setImageList(prevList); alert('이미지 삭제에 실패했어요. 다시 시도해주세요!'); }, }, ); };frontend/src/components/common/InputField/InputField.stories.tsx (1)
80-231: 스토리 렌더 로직 중복은 DRY 가능 (선택)
모든 스토리가 동일한 useState + onChange/onClear 패턴을 반복합니다. 공통 render 헬퍼로 중복을 줄일 수 있습니다.frontend/src/hooks/Queries/useClub.ts (1)
15-16: 동일 모듈의 중복 default import 정리 제안
같은 파일에서 두 번 default import되어 의미가 혼동됩니다. 하나로 통일하면 가독성이 좋아집니다.♻️ 정리 예시
-import convertToDriveUrl from '@/utils/convertGoogleDriveUrl'; -import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; +import convertGoogleDriveUrl from '@/utils/convertGoogleDriveUrl'; @@ - logo: convertToDriveUrl(club.logo), + logo: convertGoogleDriveUrl(club.logo),
frontend/src/components/common/CustomDropDown/CustomDropDown.stories.tsx
Show resolved
Hide resolved
frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
Show resolved
Hide resolved
- wrapped({ data: {...} })와 unwrapped({ clubId, message })
응답 형식을 모두 처리하도록 handleResponse 수정
- 하드코딩된 OPTIONS 대신 args.options 사용하여 Storybook Controls에서 옵션 변경 시 UI에 즉시 반영되도록 수정
- detail -> description
- 수동 API 호출 대신 훅 사용으로 중복된 invalidateQueries 로직 제거
- window.open() 대신 동적 a 태그 생성 방식으로 변경 - iOS Safari의 비동기 컨텍스트 팝업 차단 문제 해결 - 코드 간소화 (복잡한 fallback 로직 제거)
This reverts commit 595da9b.
- API 호출 전에 미리 빈 창을 열어 사용자 제스처 컨텍스트 유지 - API 응답 후 외부 지원서인 경우 URL 주입 - 내부 지원서이거나 에러 발생 시 열린 창 자동 닫기
This reverts commit 17a31b9.
- handleResponse 제네릭 타입 도입 - null/undefined 데이터 검증 강화 - API 함수 반환 타입 명시
…able-MOA-534 [fix] 외부지원서 리다이렉트 방식 window.location.href로 변경
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (1)
90-93: 모달 열기 전에 에러 처리 추가 필요에러 발생 시 모달을 열면
applicationOptions가 비어있어 "지원 가능한 분야가 없습니다"라는 메시지만 표시됩니다. 사용자가 API 오류인지 실제 지원서가 없는 상태인지 구분할 수 없는 UX 문제가 있습니다. 에러 발생 시 모달을 열지 않고 에러 알림을 표시하거나, 모달 내에서 구분된 에러 상태를 안내하는 것이 좋습니다.
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx (1)
30-56: 내부 폼에서 불필요한 빈 탭이 열릴 수 있어요.
현재 모든 경로에서window.open을 먼저 호출해 내부 폼에서도 잠깐 빈 탭이 열렸다 닫힐 수 있습니다. 외부 여부를 미리 알 수 있다면 외부 폼일 때만 새 탭을 여는 분기 고려해 주세요.
…ing-MOA-532 [Refactor] hooks 리팩토링 PR에 대한 추가수정
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@frontend/src/apis/application.ts`:
- Around line 29-52: Replace the English error label passed to withErrorHandling
in deleteApplication with a Korean message to match other functions: change the
second argument in deleteApplication (currently 'Error fetching delete
application') to a Korean string such as '지원서 삭제 중 오류 발생:' so it uses the same
withErrorHandling pattern as createApplication; locate the deleteApplication
function and update that argument accordingly.
In `@frontend/src/apis/image.ts`:
- Around line 136-157: The handleResponse error messages inside logoApi
functions are missing the "로고" prefix; update the failure messages in
getUploadUrl (the handleResponse call that currently reads `업로드 URL 생성 실패 :
${response.status}`) and in completeUpload (the handleResponse call that
currently reads `업로드 완료 처리 실패 : ${response.status}`) to include the "로고" prefix
(e.g., `로고 업로드 URL 생성 실패 : ...` and `로고 업로드 완료 처리 실패 : ...`) so they match the
outer withErrorHandling messages and the coverApi naming convention.
In `@frontend/src/hooks/Queries/useClub.ts`:
- Around line 15-16: There are duplicate imports of the same module under two
names (convertToDriveUrl and convertGoogleDriveUrl); remove the redundant import
and keep a single import (convertGoogleDriveUrl) from
'@/utils/convertGoogleDriveUrl', and update any usages that reference
convertToDriveUrl to call convertGoogleDriveUrl instead (e.g., replace
convertToDriveUrl(...) with convertGoogleDriveUrl(...)).
- Around line 85-103: The mutation expects updatedData to include an id so
onSuccess can invalidate the cache, but callers omit it; update the callers
(e.g., in ClubInfoEditTab and ClubIntroEditTab) to include clubDetail.id (or the
current club id) in the object passed to updateClubDetail so
useUpdateClubDetail's onSuccess sees variables.id and
queryClient.invalidateQueries(queryKeys.club.detail(variables.id)) runs
correctly; verify the objects constructed before calling updateClubDetail
include the id field.
♻️ Duplicate comments (2)
frontend/src/apis/auth.ts (1)
34-48: 로그아웃 시 토큰 정리와 서버 로그아웃 보장 필요
현재는 accessToken이 없으면 서버 로그아웃을 호출하지 않고, 로컬 토큰도 정리되지 않습니다. 세션/쿠키 정리 누락과 stale 토큰 사용 리스크가 있습니다.🛠️ 수정 제안
export const logout = async (): Promise<void> => { 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, '로그아웃에 실패하였습니다.'); + try { + const response = await fetch(`${API_BASE_URL}/auth/user/logout`, { + method: 'GET', + credentials: 'include', + }); + await handleResponse(response, '로그아웃에 실패하였습니다.'); + } finally { + localStorage.removeItem('accessToken'); + } }, '로그아웃 중 오류 발생'); };frontend/src/apis/application.ts (1)
154-173:active필드 타입 불일치 확인
currentStatus를 boolean으로 변환하여{ active: newStatus }로 전송하고 있으나,ApplicationFormData.active타입은'active' | 'published' | 'unpublished'(소문자 문자열)로 정의되어 있습니다. 백엔드가 실제로 boolean을 기대하는지 확인이 필요합니다.
🧹 Nitpick comments (4)
frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx (1)
135-145: Styled component를 컴포넌트 외부로 이동하세요.
ActiveListBody와ActiveApplicationRow가 컴포넌트 함수 내부에서 정의되어 있어 매 렌더링마다 새로운 styled component가 생성됩니다. 이는 불필요한 재생성과 스타일 재계산을 유발하며, React의 reconciliation 성능에도 영향을 줍니다.♻️ 제안 수정안
파일 상단(컴포넌트 외부)으로 이동:
+const ActiveListBody = styled(Styled.ApplicationList)` + border-top-left-radius: 0; +`; + +const ActiveApplicationRow = styled(ApplicationRowItem)` + &:hover { + background-color: `#f2f2f2`; + &:first-child { + border-top-right-radius: 20px; + } + } +`; + const ApplicationListTab = () => { const navigate = useNavigate(); // ... - - const ActiveListBody = styled(Styled.ApplicationList)` - border-top-left-radius: 0; - `; - const ActiveApplicationRow = styled(ApplicationRowItem)` - &:hover { - background-color: `#f2f2f2`; - &:first-child { - border-top-right-radius: 20px; - } - } - `;frontend/src/apis/image.ts (1)
5-13: 인터페이스 export 고려
PresignedData와FeedUploadRequest인터페이스가 export되지 않았습니다. 이 API를 사용하는 훅이나 컴포넌트에서 타입 안전성을 위해 해당 타입들이 필요할 수 있습니다.♻️ 인터페이스 export 제안
-interface PresignedData { +export interface PresignedData { presignedUrl: string; finalUrl: string; } -interface FeedUploadRequest { +export interface FeedUploadRequest { fileName: string; contentType: string; }frontend/src/apis/club.ts (1)
40-50: 에러 메시지 마침표 불일치Line 40의 handleResponse 메시지는 마침표가 있지만(
실패했습니다.), Line 50의 withErrorHandling 메시지는 마침표가 없습니다(실패했습니다). 일관성을 위해 통일해주세요.♻️ 수정 제안
- }, '클럽 데이터를 불러오는데 실패했습니다'); + }, '클럽 데이터를 불러오는데 실패했습니다.');frontend/src/hooks/Queries/useClub.ts (1)
25-40: 불필요한 타입 단언Line 28에서
clubId as string은 이미clubId가string타입으로 선언되어 있으므로 불필요합니다.♻️ 수정 제안
- queryFn: () => getClubDetail(clubId as string), + queryFn: () => getClubDetail(clubId),
[fix] import 중복 제거 및 클럽 정보 수정 후 캐시 무효화 개선
- window.open() 방식에서 window.location.href로 변경 - API 호출 전 빈 창을 미리 여는 로직 제거 - 외부 지원서는 현재 탭에서 열리도록 개선
#️⃣연관된 이슈
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
New Features
Refactor
Chores
✏️ Tip: You can customize this high-level summary in your review settings.