Skip to content

Conversation

@shinwokkang
Copy link
Collaborator

@shinwokkang shinwokkang commented Sep 1, 2025

💡 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)
image
  • src/types/mission/writeDiary.ts의 타입들 수정 (ApiResponse, GlobalResponse 사용을 위함) -> src/apis/mission/avatarApis -> src/hooks/mission 의 hook들 type들도 수정.

🤔 추후 작업 예정

  • 추가 구현이 필요한 부분이나 다음 작업 계획을 작성해 주세요.
  • UI 틀 수정.. ?

📸 작업 결과 (스크린샷)

  • 작업 결과를 보여주는 스크린샷을 첨부해 주세요.
  • avatar.ts
image image image

🔗 관련 이슈

Summary by CodeRabbit

  • Refactor
    • 아바타/퀴즈/일기 API 응답 포맷을 일관된 래퍼 형태로 표준화하고 데이터 구조를 평탄화해 화면 데이터 처리 단순화
    • 관련 훅 전반에 오류 타입 정교화 및 선택(Select) 단계에서 결과만 노출하도록 정리
  • 개선
    • 일부 요청에 자동 재시도(1회) 추가로 네트워크 일시 오류에 대한 안정성 향상
    • 오류 메시지를 한국어로 명확화하여 문제 파악 용이
    • 일기 이미지 업로드 후 목록 캐시를 항상 갱신해 최신 상태 보장
  • UI
    • 아바타 선택 상세 페이지가 즉시 배열 데이터를 사용해 로딩/선택 흐름을 간소화

@shinwokkang shinwokkang self-assigned this Sep 1, 2025
@shinwokkang shinwokkang added the ♻️Refactor 리팩토링 label Sep 1, 2025
@shinwokkang shinwokkang linked an issue Sep 1, 2025 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link

coderabbitai bot commented Sep 1, 2025

Walkthrough

아바타, 퀴즈, 일기 작성 API/훅 전반에 ApiResponse/GlobalResponse 래퍼를 도입하고, 관련 타입을 래퍼 제거된 평면 구조로 재정의했습니다. 훅들은 AxiosError 기반 에러 타입으로 갱신되고 select를 통해 result 언래핑을 적용했습니다. 일부 경로 문자열 변경과 페이지 데이터 참조 방식이 이에 맞게 수정되었습니다.

Changes

Cohort / File(s) Summary
Avatars API 표준화
src/apis/avatars/avatarApi.ts
모든 메서드 반환을 ApiResponse/GlobalResponse 기반으로 변경. 함수 시그니처 업데이트 및 에러 처리 단순화. 관련 타입 임포트 추가.
Avatars 훅 갱신
src/hooks/avatars/useFinalChoiceAvatarApi.ts, src/hooks/avatars/useGetSelectAvatarApi.ts
useMutation/useQuery 제네릭을 GlobalResponse/ApiResponse + AxiosError로 변경. select로 result 언래핑 또는 data.result 접근 반영. retry 옵션 추가 및 에러 로깅 개선.
Avatars 타입 조정
src/types/avatars/masters.ts
SelectAvatarResponse 제거. FinalChoiceAvatarResponse를 string 별칭으로 단순화.
선택 상세 페이지 적응
src/pages/registration/SelectionDetailPage.tsx
data.result 의존 제거 후 배열 직접 사용으로 로직/렌더링 업데이트.
퀴즈 API 표준화
src/apis/missions/QuizApi.ts
getQuizApi를 ApiResponse 래퍼 반환으로, postAnswerQuizApi를 GlobalResponse 반환으로 변경. 파라미터 타입 정제 및 에러 메시지 로컬라이즈.
퀴즈 훅 갱신
src/hooks/mission/useGetQuizApi.ts, src/hooks/mission/usePostAnswerQuiz.ts
useQuery/useMutation 제네릭을 ApiResponse/GlobalResponse + AxiosError로 변경. select로 데이터 평면화, 성공/실패 로깅 추가, retry/옵션 지정.
일기 작성 API 표준화
src/apis/missions/writeDiaryApi.ts
두 API가 ApiResponse 반환으로 변경. 내부 try/catch 제거, then 체인으로 data 반환. writeDiarySubmitApi 경로가 "api/v1/diaries"로 변경(선행 슬래시 제거).
일기 작성 훅 갱신
src/hooks/mission/useWriteDiaryApi.ts
useMutation 제네릭을 GlobalResponse + AxiosError로 변경. 업로드 입력 타입을 WriteDiaryImageUploadRequest로 변경. onSettled로 캐시 무효화, retry 추가, 에러 로깅 개선.
퀴즈 타입 평면화
src/types/realQuiz/getQuiz.ts, src/types/realQuiz/answerQuiz.ts
getQuizResponse/AnswerQuizResponse에서 래퍼 제거 후 필드를 최상위로 승격.
일기 작성 타입 평면화/요청 타입 추가
src/types/mission/writeDiary.ts
WriteDiaryImageUploadRequest 추가. 업로드/제출 응답을 평면 구조로 변경.

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 등
Loading
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로 선택 확인
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • BeanMouse

Poem

꼬박꼬박 래퍼를 둘러 싼다, 삐약-📦
result를 벗겨 보니, 데이터 반짝! ✨
토끼 발로 탁, 에러는 살짝-로그로 착
후딱후딱 훅도 정렬, 재시도 한 박
오늘 커밋, 평평한 들판처럼 맑다 🐰

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/82-typeRefactor

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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가 발생할 수 있습니다. 키를 원시값으로 축약하거나, useMemoparams 안정화하는 것을 권장합니다.

예: 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 이름이므로 postCrationAvatarApipostCreationAvatarApi로 통일하고 사용처를 함께 변경해주세요.

  • 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.

📥 Commits

Reviewing files that changed from the base of the PR and between c8c1cf9 and 10f09b0.

📒 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에서의 캐시 무효화 전략이 적절합니다.

Comment on lines +13 to 15
export const getSelectionAvatarApi = async (): ApiResponse<AvatarType> => {
return axios.get("/api/v1/avatars/masters").then(res => res.data);
};
Copy link

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.getGlobalResponse 제네릭을 지정해 타입 안전성을 확보해 주세요.

-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 파일을 찾을 수 없습니다."
fi

Length 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);
Copy link

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/**' -C2

Length 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.

Suggested change
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").

Comment on lines +10 to +17
return useQuery<
GlobalResponse<AvatarType[]>,
AxiosError<ErrorResponse>,
AvatarType[]
>({
queryKey: ["selectionAvatar"],
queryFn: getSelectionAvatarApi,
select: data => data.result,
Copy link

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 src

Length 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.

Comment on lines +10 to +14
return useQuery<
ApiResponse<getQuizResponse>, // 서버 전체 응답 타입
AxiosError<ErrorResponse>, // Axios 확장 에러 타입
getQuizResponse // select로 실제 사용할 데이터 타입
>({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

useQuery 제네릭 오용 및 select 누락으로 인한 타입/런타임 불일치

  • 현재 첫 번째 제네릭에 ApiResponse<...>(= Promise<GlobalResponse<...>>)를 넣어 Promise가 중첩됩니다. TQueryFnData는 “해결된” 데이터(= GlobalResponse<T>)여야 합니다.
  • TDatagetQuizResponse로 지정했지만 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.

Suggested change
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
});
}

Comment on lines 18 to +26
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

publicisPublic 네이밍 불일치

요청은 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/types

Length 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 src

Length of output: 3626


useWriteDiarySubmitApi 훅에서 publicisPublic으로 매핑되는지 확인합니다.


🏁 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.ts

Length 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.ts

Length of output: 897


isPublic vs public 네이밍 불일치
writeDiarySubmitRequestisPublic, writeDiarySubmitResponsepublic을 사용해 매핑 중복이 예상됩니다. API 응답 타입을 isPublic으로 통일하거나, useWriteDiarySubmitApi 훅의 mutationFn에서 publicisPublic 매핑 로직을 추가하세요.

🤖 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.

Comment on lines +12 to +19
quizOptions:
| [
{
optionOrder: number;
optionText: string;
},
]
| null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️Refactor 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] 전역 타입으로 타입 변환

2 participants