-
Notifications
You must be signed in to change notification settings - Fork 1
[Refactor] 아바타 생성/선택 및 퀴즈 타입 리팩토링 #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Walkthrough아바타, 퀴즈, 일기 작성 API/훅 전반에 ApiResponse/GlobalResponse 래퍼를 도입하고, 관련 타입을 래퍼 제거된 평면 구조로 재정의했습니다. 훅들은 AxiosError 기반 에러 타입으로 갱신되고 select를 통해 result 언래핑을 적용했습니다. 일부 경로 문자열 변경과 페이지 데이터 참조 방식이 이에 맞게 수정되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor UI as Component/Page
participant Hook as React Query Hook
participant API as axios API fn
participant BE as Backend
UI->>Hook: useQuery/useMutation 호출
Hook->>API: 요청 수행 (axios)
API->>BE: HTTP 요청
BE-->>API: GlobalResponse<ApiData> 또는 ApiResponse<ApiData>
API-->>Hook: response.data
alt useQuery(select 사용)
Hook-->>UI: data.result 또는 언래핑된 data
else useMutation
Hook-->>UI: data.result (필요 시)
end
Note over Hook,UI: 에러 타입: AxiosError<ErrorResponse><br/>옵션: retry 등
sequenceDiagram
autonumber
actor User as User
participant Page as SelectionDetailPage
participant Hook as useGetSelectAvatar
participant API as getSelectionAvatarApi
User->>Page: 페이지 진입
Page->>Hook: 아바타 목록 요청
Hook->>API: GET /avatars (예)
API-->>Hook: ApiResponse<AvatarType[]>
Hook-->>Page: select로 AvatarType[] 전달
Page->>Page: data[0].id로 초기 선택 설정
User->>Page: Next 클릭
Page->>Page: data.find로 선택 확인
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (22)
src/types/avatars/masters.ts (1)
20-20: 응답을 string으로 두는 대신 의미 있는 구조체로 명시하는 것을 고려해주세요.
FinalChoiceAvatarResponse = string은 “매직 문자열”로 남아 추후 확장/검증이 어렵습니다. 최소한 명시적 키를 둔 객체가 유지보수에 유리합니다.다음과 같이 제안드립니다(백엔드 계약 확인 필요):
-export type FinalChoiceAvatarResponse = string; +export interface FinalChoiceAvatarResponse { + message: string; // or { selectedMasterId: number } 등 실제 계약에 맞게 +}이 변경이 가능한지 서버 스펙을 한 번만 확인 부탁드립니다.
src/types/realQuiz/answerQuiz.ts (1)
6-12: 퀴즈 타입 일관성 및 프로퍼티 네이밍 정합성 확인 필요
quizType이 여기선"OX" | "MULTI_CHOICE"인데, getQuiz 쪽은"CHOICE_WITH_PICTURE"까지 포함합니다. 동일 셋을 유지하거나, 해당 타입이 응답상 절대 오지 않는 이유를 주석으로 남겨주세요.- 요청(
AnswerQuizRequest)은selectedOptionOrder, 응답은selectedOptionNumber로 네이밍이 달라 혼동을 유발합니다. 한쪽으로 통일하는 것을 권장합니다.가능 시 아래처럼 정리 제안:
export interface AnswerQuizResponse { quizQuestion: string; - quizType: "OX" | "MULTI_CHOICE"; - selectedOptionNumber: number; + quizType: "OX" | "MULTI_CHOICE" | "CHOICE_WITH_PICTURE"; + selectedOptionOrder: number; answerNumber: number; answerDescription: string; isCorrect: boolean; isCompleted: boolean; }src/types/realQuiz/getQuiz.ts (1)
6-9: 사진형 보기(CHOICE_WITH_PICTURE) 옵션 스키마 확인사진형에서 옵션에 이미지 URL이 필요하다면
quizOptions원소에optionImageUrl?: string등의 필드가 있어야 합니다. 현재 스키마로 충분한지 서버 응답 샘플로 검증 부탁드립니다.원하시면 스키마 가드(Zod/Valibot)도 함께 제안드리겠습니다.
src/types/mission/writeDiary.ts (1)
5-8: 타입 네이밍 컨벤션 통일(PascalCase 권장)동일 파일 내에서
WriteDiaryImageUploadRequest(PascalCase)와writeDiaryImageUploadResponse(camelCase)가 혼재되어 있습니다. 전반적으로 PascalCase로 맞추는 것을 권장합니다.-export type writeDiaryImageUploadResponse = { +export interface WriteDiaryImageUploadResponse { imageId: number; imageUrl: string; -}; +}참고: 다른 곳의
writeDiarySubmitRequest/Response도 동일하게 정리하면 가독성이 좋아집니다.src/hooks/mission/useGetQuizApi.ts (1)
15-16: queryKey 안정성
params가 렌더마다 새로운 객체면 불필요한 refetch가 발생할 수 있습니다. 키를 원시값으로 축약하거나,useMemo로params안정화하는 것을 권장합니다.예:
queryKey: ["quiz", params.quizType]src/pages/registration/SelectionDetailPage.tsx (3)
19-23: 데이터 변경 시 selectedId 동기화 보강 필요현재는 최초 로드 시에만 초기화됩니다. 데이터가 바뀌어 기존 selectedId가 더 이상 존재하지 않는 경우를 대비해 재동기화가 필요합니다.
- useEffect(() => { - if (data && data.length > 0 && selectedId === null) { - setSelectedId(data[0].id); - } - }, [data, selectedId]); + useEffect(() => { + if (!data || data.length === 0) { + if (selectedId !== null) setSelectedId(null); + return; + } + if (selectedId === null || !data.some(a => a.id === selectedId)) { + setSelectedId(data[0].id); + } + }, [data, selectedId]);
26-31: 가드 조건과 탐색 로직 간결화빈 배열 대비해 length 체크를 추가하고, 불필요한 타입 주석을 제거해 가독성을 높일 수 있습니다.
- if (selectedId === null || !data) return; - - const selectedAvatar = data.find( - (avatar: AvatarType) => avatar.id === selectedId - ); + if (selectedId == null || !data?.length) return; + const selectedAvatar = data.find(avatar => avatar.id === selectedId);
64-71: 빈 상태(아바타 없음) UI 처리 제안데이터가 빈 배열이면 본문이 비어 보일 수 있습니다. 간단한 플레이스홀더를 권장합니다.
- {data && selectedId !== null && ( - <SelectionDetail - avatars={data} - selectedId={selectedId} - onSelect={setSelectedId} - /> - )} + {Array.isArray(data) && data.length > 0 ? ( + <SelectionDetail + avatars={data} + selectedId={selectedId} + onSelect={setSelectedId} + /> + ) : ( + <div className="p-6.25 text-body2 text-gray-500"> + 선택 가능한 아바타가 없습니다. + </div> + )}src/hooks/mission/usePostAnswerQuiz.ts (3)
15-21: 변이 결과를 언래핑해 훅 소비자 경험을 통일쿼리 훅에서는 select로 result를 언래핑합니다. 변이도 동일하게 언래핑하면 호출부에서 일관된 타입(AnswerQuizResponse)을 사용할 수 있습니다.
- return useMutation< - GlobalResponse<AnswerQuizResponse>, // 성공 응답 타입 - AxiosError<ErrorResponse>, // 에러 타입 - AnswerQuizParams // 요청 파라미터 타입 - >({ - mutationFn: ({ quizId, selectedOptionOrder }: AnswerQuizParams) => - postAnswerQuizApi({ quizId, selectedOptionOrder }), + return useMutation< + AnswerQuizResponse, + AxiosError<ErrorResponse>, + AnswerQuizParams + >({ + mutationFn: ({ quizId, selectedOptionOrder }: AnswerQuizParams) => + postAnswerQuizApi({ quizId, selectedOptionOrder }).then(res => res.result),
22-30: 콘솔 로그는 환경 가드 또는 호출부로 이동 권장훅 내부 로깅은 중복/노이즈가 될 수 있습니다. 필요 시 DEV 환경 가드로 보호하거나 호출부(onSuccess/onError 인자)에서 처리하세요.
9-12: 요청 파라미터 타입 중복 정의 제거 제안이미 AnswerQuizRequest가 있으므로 재사용하면 DRY해집니다.
변경 제안(선택):
// 기존 interface AnswerQuizParams { quizId: number; selectedOptionOrder: number; } // 제안 type AnswerQuizParams = AnswerQuizRequest & { quizId: number };src/hooks/avatars/useGetSelectAvatarApi.ts (1)
18-21: gcTime 및 staleTime 설정 검토
버전 ^5.81.5(v5)이므로 gcTime 옵션 사용은 올바릅니다. 하지만 staleTime(120분)에 비해 gcTime(5분)이 짧아 언마운트 5분 후 캐시가 제거되고 staleTime 이내라도 재패칭이 발생할 수 있습니다. 의도에 따라 gcTime ≥ staleTime(또는 적절한 값)으로 조정하세요.src/apis/missions/QuizApi.ts (2)
12-14: axios 제네릭으로 응답 타입 명시런타임/컴파일 타이핑을 강화하기 위해 axios.get에 제네릭을 부여하세요.
-): ApiResponse<getQuizResponse> => { - return axios.get("/api/v1/realQuiz", { params }).then(res => res.data); +): ApiResponse<getQuizResponse> => { + return axios + .get<GlobalResponse<getQuizResponse>>("/api/v1/realQuiz", { params }) + .then(res => res.data);
22-31: 에러 처리 스타일 일관화(try/catch 제거 또는 통일)API 레이어에서 콘솔 로그 후 재던지기는 훅 수준 로깅과 중복됩니다. 단순 위임 형태로 통일을 권장합니다.
-export const postAnswerQuizApi = async ({ +export const postAnswerQuizApi = async ({ selectedOptionOrder, quizId, }: AnswerQuizRequest & { quizId: number }): Promise< GlobalResponse<AnswerQuizResponse> > => { - try { - const response = await axios.post<GlobalResponse<AnswerQuizResponse>>( - `/api/v1/realQuiz/${quizId}/answer`, - { selectedOptionOrder } - ); - return response.data; - } catch (error) { - console.error("퀴즈 답안 제출 실패:", error); - throw error; - } + return axios + .post<GlobalResponse<AnswerQuizResponse>>( + `/api/v1/realQuiz/${quizId}/answer`, + { selectedOptionOrder } + ) + .then(res => res.data); };src/hooks/avatars/useFinalChoiceAvatarApi.ts (2)
14-21: 변이 결과 언래핑으로 소비자 단순화쿼리 훅과 동일하게 result를 언래핑해 훅 소비자가 문자열만 받도록 하는 편이 일관됩니다.
- return useMutation< - GlobalResponse<FinalChoiceAvatarResponse>, - AxiosError<ErrorResponse>, - FinalChoiceAvatarRequest - >({ - mutationFn: data => finalChoiceAvatarApi(data), - onSuccess: data => { - console.log("아바타 최종 선택 성공:", data.result); + return useMutation< + FinalChoiceAvatarResponse, + AxiosError<ErrorResponse>, + FinalChoiceAvatarRequest + >({ + mutationFn: data => + finalChoiceAvatarApi(data).then(res => res.result), + onSuccess: result => { + console.log("아바타 최종 선택 성공:", result);
23-27: 훅 내부 alert 제거 및 retry 옵션 제거 후 호출부에서 처리 위임
- onError 내부의 alert 제거, console.error만 유지
- retry:1 옵션 제거하여 기본 재시도(0) 사용 (비멱등성 mutation에서는 재시도 지양)
onError: error => { - alert("아바타 최종 선택에 실패했습니다."); console.error(error.response?.data?.message ?? error.message); }, -retry: 1,src/apis/avatars/avatarApi.ts (2)
21-25: 외부 호출 보안/신뢰성: HTTP 사용 및 타임아웃 미설정
- 평문 HTTP 전송은 이미지 업로드에 부적절합니다. 가능한 경우 HTTPS로 전환하거나 환경변수로 분기하세요.
- 타임아웃(예: 30s)을 설정해 hang 방지하세요.
- const response = await Axios.post<GlobalResponse<CreateAvatarResponse>>( - "http://43.200.84.255:8000/process-image", - formData - ); + const base = process.env.NEXT_PUBLIC_IMAGE_SERVER_URL ?? "http://43.200.84.255:8000"; + const response = await Axios.post<GlobalResponse<CreateAvatarResponse>>( + `${base}/process-image`, + formData, + { timeout: 30_000 } + );실서버에서는 TLS 사용 여부와 엔드포인트를 확인해 주세요.
17-20: postCrationAvatarApi 함수명 오타 수정public API 이름이므로
postCrationAvatarApi→postCreationAvatarApi로 통일하고 사용처를 함께 변경해주세요.
- src/apis/avatars/avatarApi.ts
-export const postCrationAvatarApi = async ( +export const postCreationAvatarApi = async (
- src/hooks/avatars/usePostCreationAvatarApi.tsx
-import { postCrationAvatarApi } from "@/apis/avatars/avatarApi"; +import { postCreationAvatarApi } from "@/apis/avatars/avatarApi"; - mutationFn: formData => postCrationAvatarApi(formData), + mutationFn: formData => postCreationAvatarApi(formData),src/apis/missions/writeDiaryApi.ts (2)
12-16: 타입 안전성 강화: 제네릭 지정 권장응답 래퍼를 명시하면 오타/스키마 변화에 더 빨리 반응할 수 있습니다. 상단 import도 함께 보강해 주세요.
-import { ApiResponse } from "@/types/common/apiResponse.type"; +import { ApiResponse, GlobalResponse } from "@/types/common/apiResponse.type"; ... - return axios.post("/api/v1/diaries/images", formData).then(res => res.data); + return axios + .post<GlobalResponse<writeDiaryImageUploadResponse>>( + "/api/v1/diaries/images", + formData + ) + .then(res => res.data);
18-22: 불필요한 async 키워드 제거로 일관성 개선
async없이 프로미스를 반환하는 형태가 위 함수와 일관됩니다.-export const writeDiarySubmitApi = async ( - params: writeDiarySubmitRequest -): ApiResponse<writeDiarySubmitResponse> => { - return axios.post("/api/v1/diaries", params).then(res => res.data); -}; +export const writeDiarySubmitApi = ( + params: writeDiarySubmitRequest +): ApiResponse<writeDiarySubmitResponse> => { + return axios + .post<GlobalResponse<writeDiarySubmitResponse>>("/api/v1/diaries", params) + .then(res => res.data); +};src/hooks/mission/useWriteDiaryApi.ts (2)
31-34: 에러 로깅: 메시지 중심으로 간결화에러 객체 전체 대신 서버 표준 메시지를 우선 표시하면 로그/알림이 명확해집니다.
- console.error( - "이미지 업로드 실패:", - error.response?.data || error.message - ); + console.error( + "이미지 업로드 실패:", + error.response?.data?.message ?? error.message + );
55-56: 에러 로깅 일관화 제안위와 동일하게 메시지 중심으로 통일하면 좋습니다.
- console.error("일기 작성 실패:", error.response?.data || error.message); + console.error( + "일기 작성 실패:", + error.response?.data?.message ?? error.message + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (13)
src/apis/avatars/avatarApi.ts(1 hunks)src/apis/missions/QuizApi.ts(1 hunks)src/apis/missions/writeDiaryApi.ts(2 hunks)src/hooks/avatars/useFinalChoiceAvatarApi.ts(1 hunks)src/hooks/avatars/useGetSelectAvatarApi.ts(1 hunks)src/hooks/mission/useGetQuizApi.ts(1 hunks)src/hooks/mission/usePostAnswerQuiz.ts(2 hunks)src/hooks/mission/useWriteDiaryApi.ts(3 hunks)src/pages/registration/SelectionDetailPage.tsx(2 hunks)src/types/avatars/masters.ts(2 hunks)src/types/mission/writeDiary.ts(2 hunks)src/types/realQuiz/answerQuiz.ts(1 hunks)src/types/realQuiz/getQuiz.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/apis/missions/writeDiaryApi.ts (2)
src/types/common/apiResponse.type.ts (1)
ApiResponse(9-9)src/types/mission/writeDiary.ts (3)
writeDiaryImageUploadResponse(5-8)writeDiarySubmitRequest(10-16)writeDiarySubmitResponse(18-27)
src/hooks/avatars/useGetSelectAvatarApi.ts (3)
src/types/common/apiResponse.type.ts (2)
GlobalResponse(1-6)ErrorResponse(15-15)src/types/avatars/masters.ts (1)
AvatarType(2-6)src/apis/avatars/avatarApi.ts (1)
getSelectionAvatarApi(13-15)
src/hooks/mission/usePostAnswerQuiz.ts (3)
src/types/common/apiResponse.type.ts (2)
GlobalResponse(1-6)ErrorResponse(15-15)src/types/realQuiz/answerQuiz.ts (1)
AnswerQuizResponse(5-13)src/apis/missions/QuizApi.ts (1)
postAnswerQuizApi(16-32)
src/apis/avatars/avatarApi.ts (2)
src/types/common/apiResponse.type.ts (2)
ApiResponse(9-9)GlobalResponse(1-6)src/types/avatars/masters.ts (4)
AvatarType(2-6)CreateAvatarResponse(9-11)FinalChoiceAvatarRequest(14-18)FinalChoiceAvatarResponse(20-20)
src/hooks/mission/useGetQuizApi.ts (3)
src/types/realQuiz/getQuiz.ts (2)
getQuizRequest(1-3)getQuizResponse(5-20)src/types/common/apiResponse.type.ts (2)
ApiResponse(9-9)ErrorResponse(15-15)src/apis/missions/QuizApi.ts (1)
getQuizApi(10-14)
src/hooks/mission/useWriteDiaryApi.ts (3)
src/types/common/apiResponse.type.ts (2)
GlobalResponse(1-6)ErrorResponse(15-15)src/types/mission/writeDiary.ts (4)
writeDiaryImageUploadResponse(5-8)WriteDiaryImageUploadRequest(1-3)writeDiarySubmitResponse(18-27)writeDiarySubmitRequest(10-16)src/apis/missions/writeDiaryApi.ts (1)
takePhotoUploadApi(12-16)
src/apis/missions/QuizApi.ts (3)
src/types/realQuiz/getQuiz.ts (2)
getQuizRequest(1-3)getQuizResponse(5-20)src/types/common/apiResponse.type.ts (2)
ApiResponse(9-9)GlobalResponse(1-6)src/types/realQuiz/answerQuiz.ts (2)
AnswerQuizRequest(1-3)AnswerQuizResponse(5-13)
src/hooks/avatars/useFinalChoiceAvatarApi.ts (3)
src/types/common/apiResponse.type.ts (2)
GlobalResponse(1-6)ErrorResponse(15-15)src/types/avatars/masters.ts (2)
FinalChoiceAvatarResponse(20-20)FinalChoiceAvatarRequest(14-18)src/apis/avatars/avatarApi.ts (1)
finalChoiceAvatarApi(32-44)
🔇 Additional comments (4)
src/types/avatars/masters.ts (1)
1-1: 주석 컨벤션 유지 좋습니다.파일 상단 주석으로 타입의 역할이 분명합니다.
src/types/mission/writeDiary.ts (1)
1-3: 요청 타입 분리 좋습니다업로드가
FormData기반임을 명시해 사용처에서 타입 안전성이 올라갑니다.src/apis/avatars/avatarApi.ts (1)
36-39: 제네릭 지정 및 래핑 일관성 LGTM
GlobalResponse<FinalChoiceAvatarResponse>로의 제네릭 지정과response.data반환 패턴이 프로젝트 전반의 래퍼 타입과 일치합니다.src/hooks/mission/useWriteDiaryApi.ts (1)
21-37: 뮤테이션 제네릭/정착 처리 LGTM데이터/에러 제네릭 및
onSettled에서의 캐시 무효화 전략이 적절합니다.
| export const getSelectionAvatarApi = async (): ApiResponse<AvatarType> => { | ||
| return axios.get("/api/v1/avatars/masters").then(res => res.data); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
타입 불일치: 배열 반환으로 수정 + 제네릭 지정 누락
해당 엔드포인트는 선택 가능한 아바타 목록을 반환하므로 배열 타입이 맞습니다. 또한 axios.get에 GlobalResponse 제네릭을 지정해 타입 안전성을 확보해 주세요.
-export const getSelectionAvatarApi = async (): ApiResponse<AvatarType> => {
- return axios.get("/api/v1/avatars/masters").then(res => res.data);
-};
+export const getSelectionAvatarApi = (): ApiResponse<AvatarType[]> => {
+ return axios
+ .get<GlobalResponse<AvatarType[]>>("/api/v1/avatars/masters")
+ .then(res => res.data);
+};관련 훅(src/hooks/avatars/useGetSelectAvatarApi.ts)과 페이지가 AvatarType[]을 기대하는지 최종 확인 부탁드립니다.
🏁 Script executed:
#!/bin/bash
# 1. 파일 존재 여부 확인
echo "🔍 Searching for avatarApi and hook files"
API_FILE=$(fd --hidden --ignore-case 'avatarApi.ts' -p)
HOOK_FILE=$(fd --hidden --ignore-case 'useGetSelectAvatarApi.ts' -p)
echo "API_FILE: $API_FILE"
echo "HOOK_FILE: $HOOK_FILE"
# 2. API 함수 정의 확인
if [[ -f "$API_FILE" ]]; then
echo "🔍 Contents of $API_FILE (lines 1-50):"
sed -n '1,50p' "$API_FILE"
else
echo "❌ $API_FILE not found"
fi
# 3. Hook 파일에서 반환 타입 확인
if [[ -f "$HOOK_FILE" ]]; then
echo "🔍 Contents of $HOOK_FILE (lines 1-50):"
sed -n '1,50p' "$HOOK_FILE"
else
echo "❌ $HOOK_FILE not found"
fi
# 4. AvatarType 정의 위치 및 형태 확인
echo "🔍 Searching for AvatarType definition and usages"
rg -nP "type\s+AvatarType\b|interface\s+AvatarType\b" -C2 .Length of output: 2763
아래 스크립트로 API 함수 정의와 ApiResponse·GlobalResponse 타입을 확인해 주세요.
🏁 Script executed:
#!/bin/bash
# 1. avatarApi.ts 파일 내용 확인
echo "🔍 src/apis/avatars/avatarApi.ts (1-200):"
sed -n '1,200p' src/apis/avatars/avatarApi.ts || echo "❌ 파일을 찾을 수 없습니다."
# 2. apiResponse.type 파일 위치 검색
echo "🔍 apiResponse.type 파일 검색:"
TYPE_FILE=$(fd --extension ts --ignore-case 'apiResponse.type' -p src/types/common)
echo "TYPE_FILE: $TYPE_FILE"
# 3. ApiResponse·GlobalResponse 정의 확인
if [[ -n "$TYPE_FILE" ]]; then
echo "🔍 $TYPE_FILE 에서 ApiResponse·GlobalResponse 정의 확인:"
sed -n '1,200p' "$TYPE_FILE"
else
echo "❌ apiResponse.type 파일을 찾을 수 없습니다."
fiLength of output: 2240
getSelectionAvatarApi 반환 타입 및 제네릭 수정 필요
getSelectionAvatarApi시그니처를ApiResponse<AvatarType[]>로 변경axios.get호출에<GlobalResponse<AvatarType[]>>제네릭 지정- 사용 중인 훅(
useGetSelectAvatar)의AvatarType[]기대값과 일치하도록 반영해주세요.
🤖 Prompt for AI Agents
In src/apis/avatars/avatarApi.ts around lines 13 to 15, the function currently
returns ApiResponse<AvatarType> but the endpoint returns an array; change the
signature to ApiResponse<AvatarType[]> and update the axios.get call to use the
generic GlobalResponse<AvatarType[]> so the resolved data type matches the
AvatarType[] expected by useGetSelectAvatar; ensure the Promise return/then
typing aligns with these generics.
| throw error; | ||
| } | ||
| ): ApiResponse<writeDiarySubmitResponse> => { | ||
| return axios.post("api/v1/diaries", params).then(res => res.data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
경로 버그: 선행 슬래시 누락으로 베이스 경로 오조합 위험
"api/v1/diaries"는 베이스 URL에 따라 의도치 않은 path가 될 수 있습니다. 반드시 "/api/v1/diaries"로 지정해 주세요.
- return axios.post("api/v1/diaries", params).then(res => res.data);
+ return axios.post("/api/v1/diaries", params).then(res => res.data);레포 전역에서 동일한 패턴이 있는지 점검해 주세요.
🏁 Script executed:
#!/bin/bash
rg -nP '"api/v1/[^"]+"' -g '!**/node_modules/**' -C2Length of output: 358
경로 앞에 슬래시 추가 필요
axios 요청 시 상대 경로가 조합되어 잘못된 엔드포인트로 요청될 수 있습니다. 항상 절대 경로인 "/api/v1/diaries"로 지정해 주세요.
- return axios.post("api/v1/diaries", params).then(res => res.data);
+ return axios.post("/api/v1/diaries", params).then(res => res.data);레포 전역 검색 결과 해당 패턴은 이 파일 한 곳에서만 확인되었습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return axios.post("api/v1/diaries", params).then(res => res.data); | |
| return axios.post("/api/v1/diaries", params).then(res => res.data); |
🤖 Prompt for AI Agents
In src/apis/missions/writeDiaryApi.ts around line 21, the axios POST is using a
relative path "api/v1/diaries" which can be combined incorrectly with the
current URL; change it to the absolute path "/api/v1/diaries" so the request
always targets the correct endpoint (update the axios.post call to use
"/api/v1/diaries").
| return useQuery< | ||
| GlobalResponse<AvatarType[]>, | ||
| AxiosError<ErrorResponse>, | ||
| AvatarType[] | ||
| >({ | ||
| queryKey: ["selectionAvatar"], | ||
| queryFn: getSelectionAvatarApi, | ||
| select: data => data.result, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
API 반환 타입과 훅 제네릭 일치 여부 확인 필요
훅은 GlobalResponse<AvatarType[]>를 가정하고 select로 배열을 언래핑합니다. 관련 API(getSelectionAvatarApi)가 ApiResponse<AvatarType[]>를 반환하는지 확인해 주세요. 단수형이면 컴파일/런타임 불일치가 발생합니다.
다음 스크립트로 선언을 확인하세요:
🏁 Script executed:
#!/bin/bash
# getSelectionAvatarApi 시그니처와 반환 제네릭 확인
rg -nP -C2 'export\s+const\s+getSelectionAvatarApi\s*=\s*async' src | sed -n '1,120p'
rg -nP 'getSelectionAvatarApi.*ApiResponse<\s*AvatarType(\[\])?\s*>' -C1 srcLength of output: 760
API 반환 타입과 훅 제네릭 일치시키기
getSelectionAvatarApi는 현재 ApiResponse를 반환하고 있어, useQuery 훅의 제네릭(GlobalResponse<AvatarType[]>)과 불일치합니다. 배열 언래핑(select) 시 컴파일/런타임 에러가 발생하므로, 제네릭을 GlobalResponse으로 변경하거나 getSelectionAvatarApi의 반환 타입을 ApiResponse<AvatarType[]>로 수정하세요.
🤖 Prompt for AI Agents
In src/hooks/avatars/useGetSelectAvatarApi.ts around lines 10-17, the useQuery
generics expect GlobalResponse<AvatarType[]> but getSelectionAvatarApi currently
returns ApiResponse<AvatarType>, causing a mismatch when select unwraps an
array; fix by making the types consistent: either change the hook generics to
GlobalResponse<AvatarType> (and adjust select to return a single AvatarType) or
update getSelectionAvatarApi to return ApiResponse<AvatarType[]> so useQuery can
keep GlobalResponse<AvatarType[]> and select: data => data.result remains valid;
ensure imports/types reflect the chosen shape and update any callers
accordingly.
| return useQuery< | ||
| ApiResponse<getQuizResponse>, // 서버 전체 응답 타입 | ||
| AxiosError<ErrorResponse>, // Axios 확장 에러 타입 | ||
| getQuizResponse // select로 실제 사용할 데이터 타입 | ||
| >({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useQuery 제네릭 오용 및 select 누락으로 인한 타입/런타임 불일치
- 현재 첫 번째 제네릭에
ApiResponse<...>(= Promise<GlobalResponse<...>>)를 넣어 Promise가 중첩됩니다.TQueryFnData는 “해결된” 데이터(=GlobalResponse<T>)여야 합니다. TData를getQuizResponse로 지정했지만select가 없어 실제로는 언래핑되지 않습니다. 호출부에서data.quizId를 바로 쓰면 런타임 오류가 납니다.
아래처럼 수정 제안합니다.
-import { ApiResponse, ErrorResponse } from "@/types/common/apiResponse.type";
+import { GlobalResponse, ErrorResponse } from "@/types/common/apiResponse.type";
…
- return useQuery<
- ApiResponse<getQuizResponse>, // 서버 전체 응답 타입
- AxiosError<ErrorResponse>, // Axios 확장 에러 타입
- getQuizResponse // select로 실제 사용할 데이터 타입
- >({
+ return useQuery<
+ GlobalResponse<getQuizResponse>, // 서버 전체 응답 타입(해결된 데이터)
+ AxiosError<ErrorResponse>,
+ getQuizResponse // select로 실제 사용할 데이터 타입
+ >({
queryKey: ["quiz", params],
queryFn: () => getQuizApi(params),
+ select: (data) => data.result,
staleTime: 1000 * 60 * 5, // 5분 동안 stale 처리 안함
retry: 1, // 실패 시 1번 재시도
refetchOnWindowFocus: false, // 창 포커스 시 재요청 X
});또는 ApiResponse<T>를 “Promise”가 아닌 “서버 바디”로 재정의하고, ApiPromise<T>를 새로 두는 것도 장기적으로 안전합니다(아래 별도 코멘트 참고).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return useQuery< | |
| ApiResponse<getQuizResponse>, // 서버 전체 응답 타입 | |
| AxiosError<ErrorResponse>, // Axios 확장 에러 타입 | |
| getQuizResponse // select로 실제 사용할 데이터 타입 | |
| >({ | |
| import { GlobalResponse, ErrorResponse } from "@/types/common/apiResponse.type"; | |
| import { useQuery } from "@tanstack/react-query"; | |
| import { getQuizApi, getQuizResponse } from "@/api/mission/quiz"; | |
| import type { AxiosError } from "axios"; | |
| export function useGetQuizApi(params: Parameters<typeof getQuizApi>[0]) { | |
| - return useQuery< | |
| - ApiResponse<getQuizResponse>, // 서버 전체 응답 타입 | |
| - AxiosError<ErrorResponse>, // Axios 확장 에러 타입 | |
| - getQuizResponse // select로 실제 사용할 데이터 타입 | |
| return useQuery< | |
| GlobalResponse<getQuizResponse>, // 서버 전체 응답 타입(해결된 데이터) | |
| AxiosError<ErrorResponse>, | |
| getQuizResponse // select로 실제 사용할 데이터 타입 | |
| >({ | |
| queryKey: ["quiz", params], | |
| queryFn: () => getQuizApi(params), | |
| select: (data) => data.result, | |
| staleTime: 1000 * 60 * 5, // 5분 동안 stale 처리 안함 | |
| retry: 1, // 실패 시 1번 재시도 | |
| refetchOnWindowFocus: false, // 창 포커스 시 재요청 X | |
| }); | |
| } |
| export interface writeDiarySubmitResponse { | ||
| isSuccess: boolean; | ||
| code: string; | ||
| message: string; | ||
| result: { | ||
| diaryId: number; | ||
| title: string; | ||
| content: string; | ||
| imageUrl: string; | ||
| likeCount: number; | ||
| createdAt: string; | ||
| updatedAt: string; | ||
| public: boolean; | ||
| }; | ||
| diaryId: number; | ||
| title: string; | ||
| content: string; | ||
| imageUrl: string; | ||
| likeCount: number; | ||
| createdAt: string; | ||
| updatedAt: string; | ||
| public: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
public ↔ isPublic 네이밍 불일치
요청은 isPublic, 응답은 public으로 달라 추후 매핑 코드가 중복될 수 있습니다. API 계층에서 통일해 내려주거나, 훅의 select에서 매핑하는 일관 전략이 필요합니다.
예시(훅/API select에서 매핑):
select: (res) => ({ ...res.result, isPublic: res.result.public })네이밍 일관성 검증을 위해 아래 스크립트를 실행합니다.
🏁 Script executed:
#!/bin/bash
# response 인터페이스 확인
rg -n --type=ts "export interface writeDiarySubmitResponse" -C2 src/types/mission/writeDiary.ts
# request 인터페이스 (isPublic 포함) 정의 위치 확인
rg -n --type=ts "export interface .*WriteDiary.*Request" -C2 src/types/mission
# 코드베이스 내 isPublic 사용 위치 검색
rg -n "isPublic" -C3 src
# 타입 정의 내 public 프로퍼티 사용 위치 검색
rg -n "public:" -C2 src/typesLength of output: 9436
추가 매핑 로직 확인을 위해 아래 스크립트를 실행해 주세요.
🏁 Script executed:
#!/bin/bash
# WriteDiaryPage.tsx 상단 import 및 hook 사용 확인
sed -n '1,30p' src/pages/dailyMission/WriteDiaryPage.tsx
# useWriteDiarySubmitApi 정의 위치 확인
rg -n "useWriteDiarySubmitApi" -C5 srcLength of output: 3626
useWriteDiarySubmitApi 훅에서 public이 isPublic으로 매핑되는지 확인합니다.
🏁 Script executed:
#!/bin/bash
# useWriteDiaryApi.ts 전체에서 매핑 또는 select 옵션 사용 여부 확인
sed -n '1,200p' src/hooks/mission/useWriteDiaryApi.ts
# useWriteDiarySubmitApi 함수 정의 블록 확인
sed -n '35,100p' src/hooks/mission/useWriteDiaryApi.ts
# select, onSuccess 등 매핑 로직 검색
rg -nE "(select|onSuccess)" -C3 src/hooks/mission/useWriteDiaryApi.ts
# response 내부 public 참조 검색
rg -n "res\.result\.public" -C3 src/hooks/mission/useWriteDiaryApi.tsLength of output: 2414
🏁 Script executed:
#!/bin/bash
# writeDiary 타입 파일에서 request 인터페이스 확인
rg -n 'export interface writeDiarySubmitRequest' -C5 src/types/mission/writeDiary.ts
# request 인터페이스 전체 내용 출력
sed -n '1,200p' src/types/mission/writeDiary.tsLength of output: 897
isPublic vs public 네이밍 불일치
writeDiarySubmitRequest는 isPublic, writeDiarySubmitResponse는 public을 사용해 매핑 중복이 예상됩니다. API 응답 타입을 isPublic으로 통일하거나, useWriteDiarySubmitApi 훅의 mutationFn에서 public→isPublic 매핑 로직을 추가하세요.
🤖 Prompt for AI Agents
In src/types/mission/writeDiary.ts around lines 18 to 26, the response interface
uses the field name "public" while the request type uses "isPublic", causing a
naming mismatch; update the API contract by renaming the response property from
"public" to "isPublic" (and adjust any imports/usages) OR, if you prefer to keep
the backend response as-is, add a mapping in useWriteDiarySubmitApi's mutationFn
that converts response.public → response.isPublic before returning to callers so
the frontend consistently uses isPublic.
| quizOptions: | ||
| | [ | ||
| { | ||
| optionOrder: number; | ||
| optionText: string; | ||
| }, | ||
| ] | ||
| | null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quizOptions가 1길이 튜플로 선언되어 있습니다 — 가변 길이 배열로 수정 필요
| [ { ... } ] | null은 길이가 정확히 1인 튜플 타입입니다. 여러 보기 항목을 허용하려면 배열로 선언해야 합니다.
- quizOptions:
- | [
- {
- optionOrder: number;
- optionText: string;
- },
- ]
- | null;
+ quizOptions:
+ | Array<{
+ optionOrder: number;
+ optionText: string;
+ }>
+ | null;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| quizOptions: | |
| | [ | |
| { | |
| optionOrder: number; | |
| optionText: string; | |
| }, | |
| ] | |
| | null; | |
| quizOptions: | |
| | Array<{ | |
| optionOrder: number; | |
| optionText: string; | |
| }> | |
| | null; |
🤖 Prompt for AI Agents
In src/types/realQuiz/getQuiz.ts around lines 12 to 19, the quizOptions type is
declared as a single-element tuple (| [ { optionOrder: number; optionText:
string; } ] | null) which restricts it to exactly one item; change it to a
variable-length array by replacing the tuple with an array type (e.g. {
optionOrder: number; optionText: string; }[] | null) so multiple options are
allowed.
💡 To Reviewers
해당 브랜치에서 새롭게 설치한 라이브러리가 있다면 함께 명시해 주세요.
없습니다.
리뷰어가 코드를 이해하는 데 도움이 되는 정보나 참고사항이 있다면 자유롭게 작성해 주세요.
src/types/common/apiResponse.type.ts의 코드를 참고하여 타입들을 리팩토링 했습니다.🔥 작업 내용 (가능한 구체적으로 작성해 주세요)
src/types/avatars/masters.ts의 타입들 수정 (ApiResponse, GlobalResponse 사용을 위함) ->src/apis/avatars/avatarApis->src/hooks/avatars의 hook들 type들도 수정.Ex)
src/types/mission/writeDiary.ts의 타입들 수정 (ApiResponse, GlobalResponse 사용을 위함) ->src/apis/mission/avatarApis->src/hooks/mission의 hook들 type들도 수정.🤔 추후 작업 예정
📸 작업 결과 (스크린샷)
🔗 관련 이슈
Summary by CodeRabbit