Skip to content

feat(auth,user): 회원 인증/온보딩/프로필 수정 기능 추가#9

Merged
aryu1217 merged 3 commits intomainfrom
feat/profile
Jan 2, 2026
Merged

feat(auth,user): 회원 인증/온보딩/프로필 수정 기능 추가#9
aryu1217 merged 3 commits intomainfrom
feat/profile

Conversation

@aryu1217
Copy link
Copy Markdown
Member

@aryu1217 aryu1217 commented Jan 2, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 회원가입 흐름에 닉네임 설정 단계 추가
    • 사용자 프로필에서 닉네임 수정 기능 추가
    • 인증 후 원래 페이지로 자동 리다이렉션 기능 구현
  • 버그 수정

    • 인증 및 사용자 업데이트 과정에서 오류 처리 개선

✏️ Tip: You can customize this high-level summary in your review settings.

Summary by CodeRabbit

새 기능

  • 온보딩 플로우: 신규 사용자가 앱 진입 시 닉네임을 설정합니다.
  • 프로필 편집: 사용자가 닉네임을 언제든 수정할 수 있습니다.
  • Google 로그인 개선: 로그인 후 이전 페이지로 자동 이동합니다.

리팩토링

  • API 에러 처리 강화로 더 정확한 오류 메시지를 제공합니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 2, 2026

📝 Walkthrough

Walkthrough

인증 콜백 처리, 온보딩 마법사 및 닉네임 단계, 프로필 업데이트 API/훅과 ApiError 타입을 추가하고 Google 로그인·로그아웃 리디렉션 및 에러 처리를 개선합니다. useMe의 staleTime이 0으로 변경됩니다.

Changes

Cohort / File(s) 변경 사항
인증 콜백 / 라우팅
src/app/auth/callback/page.tsx, src/features/auth/login-with-google/api/login.ts
인증 콜백 페이지 추가; Google 로그인 시 원래 경로를 담은 next 쿼리 포함 callbackUrl 생성; 콜백에서 인증 결과에 따라 온보딩 또는 원래 경로로 리다이렉트
온보딩 UI
src/app/onboarding/page.tsx, src/features/onboarding/ui/OnboardingWizard.tsx, src/features/onboarding/ui/steps/NicknameStep.tsx
온보딩 페이지 및 마법사 컴포넌트 추가; 닉네임 입력 단계 구현 및 제출 시 completeOnboarding 호출 후 리다이렉트
온보딩 API / 훅 / 타입
src/entities/user/api/onboarding.ts, src/entities/user/hooks/useCompleteOnboarding.ts, src/entities/user/model/types.ts
completeOnboarding API 함수와 useCompleteOnboarding 훅 추가; OnboardingPayload 타입 정의
사용자 조회 변경
src/entities/user/api/me.ts, src/entities/user/hooks/useMe.ts
fetchMe의 에러 처리에서 ApiError 사용으로 변경; useMe의 staleTime을 0으로 조정
프로필 업데이트 기능
src/features/user-profile/api/updateMe.ts, src/features/user-profile/hooks/useUpdateMe.ts, src/features/user-profile/model/types.ts, src/features/user-profile/ui/NicknameEditForm.tsx
updateMe API, useUpdateMe 훅, UpdateMePayload 타입 및 닉네임 편집 폼 추가
로그아웃 변경
src/features/auth/logout/api/logout.ts, src/features/auth/logout/model/useLogout.ts
logoutlogoutApi로 네이밍 변경; ApiError 사용으로 에러 핸들링 통일; useLogout에서 mutation 제네릭/호출 방식 명시화 및 캐시 제거 로직 유지
공통 에러 타입
src/shared/api/api-error.ts
HTTP 상태를 포함하는 ApiError 클래스 추가
테스트 페이지 변경
src/app/test/page.tsx
Test 페이지에 NicknameEditForm 렌더링 추가

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Browser as 클라이언트
    participant AuthProvider as 인증 제공자
    participant AppAPI as 앱 API
    participant QueryClient as 쿼리 클라이언트

    rect rgba(200,220,255,0.6)
    Note over User,Browser: 1) Google 로그인 → 콜백으로 리디렉트
    User->>Browser: Google 로그인 클릭
    Browser->>AuthProvider: 인증 요청 (callbackUrl includes next)
    AuthProvider-->>Browser: /auth/callback?next=/원래경로 리다이렉트
    end

    rect rgba(220,240,220,0.6)
    Note over Browser,AppAPI: 2) 콜백 페이지 처리 (useMe)
    Browser->>AppAPI: GET /me (useMe)
    alt ApiError 401/403
        AppAPI-->>Browser: ApiError(401/403)
        Browser->>Browser: /onboarding?next=/원래경로로 리다이렉트
    else 성공
        AppAPI-->>Browser: user
        Browser->>QueryClient: me 쿼리 갱신
        Browser->>Browser: /원래경로로 리다이렉트
    end
    end

    rect rgba(255,240,200,0.6)
    Note over User,AppAPI: 3) 온보딩 완료 흐름
    User->>Browser: 닉네임 입력 및 제출
    Browser->>AppAPI: PATCH /user-profiles/me/onboarding (completeOnboarding)
    AppAPI-->>Browser: { result: true }
    Browser->>QueryClient: invalidateQueries(["me"])
    QueryClient->>AppAPI: refetch /me
    Browser->>Browser: next 경로로 리다이렉트
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 온보딩 소식

인증 따라 달려왔네, 콜백의 길,
닉네임 한마디로 새길을 열고,
쿼리는 춤추며 데이터 맞추고,
프로필은 반짝이며 다음 길로 향하네. ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning 제공된 설명은 저장소의 필수 템플릿 구조를 따르지 않으며, 필수 섹션들(Title, Summary, Linked Issue, Checklist, Attachments, Notes)이 누락되어 있습니다. 저장소의 설명 템플릿에 따라 Title, Summary, Linked Issue, Checklist 섹션을 포함하고 필수 정보를 기입하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 주요 변경 사항인 인증/온보딩/프로필 수정 기능 추가를 명확하게 요약하고 있으며, 변경 세트와 직접 관련이 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 7

🧹 Nitpick comments (12)
src/app/onboarding/page.tsx (1)

3-11: 불필요한 Fragment 래퍼를 제거하세요.

현재 <><div>...</div></>로 감싸고 있는데, Fragment가 불필요합니다. 또한 OnboardingWizarduseRouter, useState, useMemo 등 클라이언트 훅을 사용하고 있으며, 파일 최상단에 "use client" 지시자가 올바르게 설정되어 있어 서버 컴포넌트인 이 페이지와의 경계가 적절합니다.

🔎 불필요한 Fragment 제거 제안
 export default function OnboardingPage() {
-  return (
-    <>
-      <div>
-        <OnboardingWizard />
-      </div>
-    </>
-  );
+  return (
+    <div>
+      <OnboardingWizard />
+    </div>
+  );
 }
src/entities/user/api/me.ts (1)

17-17: 타입 캐스팅 대신 런타임 검증 또는 명시적 매핑 고려

as User 캐스팅은 API 응답의 실제 형태를 검증하지 않아 런타임 타입 불일치 위험이 있습니다.

코딩 가이드라인에 따르면 API 모듈은 "DTO → Domain 매핑까지 책임"져야 하며, raw DTO를 직접 다루지 않도록 권장됩니다.

🔎 개선 방안

방안 1: 간단한 런타임 검증 (추천)

-  return (await res.json()) as User;
+  const data = await res.json();
+  if (!data.nickname || !data.slug) {
+    throw new ApiError(500, "Invalid user data structure");
+  }
+  return data as User;

방안 2: toDomain 매핑 함수 도입

// 별도 파일에 DTO 타입과 매핑 함수 정의
type UserDTO = {
  nickname: string;
  slug: string;
  profileImageUrl: string | null;
};

function toUser(dto: UserDTO): User {
  return {
    nickname: dto.nickname,
    slug: dto.slug,
    profileImageUrl: dto.profileImageUrl,
  };
}

// API 함수에서 사용
const dto = await res.json();
return toUser(dto);

코딩 가이드라인 기준 적용

src/entities/user/hooks/useCompleteOnboarding.ts (1)

10-10: queryKey를 상수로 중앙 관리 권장

["me"] 문자열 리터럴을 직접 사용하면 다른 곳에서 동일 키를 사용할 때 오타 위험과 타입 안전성 저하가 발생합니다.

🔎 개선 방안

queryKey를 중앙 관리하는 상수 파일 생성

// src/entities/user/api/queryKeys.ts
export const userQueryKeys = {
  me: ["me"] as const,
  // 향후 추가 키들...
} as const;

hook에서 사용

+import { userQueryKeys } from "../api/queryKeys";
+
 export function useCompleteOnboarding() {
   const qc = useQueryClient();
 
   return useMutation({
     mutationFn: completeOnboarding,
     onSuccess: async () => {
-      await qc.invalidateQueries({ queryKey: ["me"] });
+      await qc.invalidateQueries({ queryKey: userQueryKeys.me });
     },
   });
 }

이렇게 하면 타입 안전성이 향상되고 queryKey 변경 시 한 곳만 수정하면 됩니다.

src/features/user-profile/api/updateMe.ts (2)

23-23: 타입 캐스팅 대신 런타임 검증 고려

me.ts의 Line 17과 동일한 이슈입니다. as User 캐스팅은 API 응답의 실제 타입을 검증하지 않아 런타임 타입 불일치 위험이 있습니다.

코딩 가이드라인의 "DTO → Domain 매핑" 권장사항에 따라 명시적 변환 계층을 고려하세요.

코딩 가이드라인 기준 적용


7-24: API 호출 로직의 공통 패턴 추상화 고려

me.ts, onboarding.ts, logout.ts 등 여러 API 파일에서 동일한 fetch → 에러 핸들링 패턴이 반복됩니다. 공통 fetch wrapper를 도입하면 코드 중복을 줄이고 유지보수성을 높일 수 있습니다.

🔎 개선 방안

공통 API 유틸리티 함수 생성

// src/shared/api/fetcher.ts
export async function apiFetch<T>(
  url: string,
  options?: RequestInit
): Promise<T> {
  const res = await fetch(`${API_BASE_URL}${url}`, {
    credentials: "include",
    headers: {
      Accept: "application/json",
      ...options?.headers,
    },
    ...options,
  });

  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new ApiError(res.status, text || res.statusText);
  }

  return res.json();
}

사용 예시

export async function updateMe(payload: UpdateMePayload): Promise<User> {
  return apiFetch<User>("/api/v1/user-profiles/me", {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
}
src/shared/api/api-error.ts (1)

1-5: 에러 클래스 구조 양호, Error.name 설정 고려

기본 구현은 적절합니다. 디버깅 편의성을 위해 Error.name을 명시적으로 설정하는 것을 고려하세요.

🔎 개선 제안
 export class ApiError extends Error {
   constructor(public status: number, message: string) {
     super(message);
+    this.name = "ApiError";
   }
 }

이렇게 하면 콘솔이나 로그에서 ApiError: ... 형태로 표시되어 일반 Error와 구분하기 쉽습니다.

src/features/user-profile/ui/NicknameEditForm.tsx (1)

35-35: 에러 메시지를 더 구체적으로 표시 권장

현재 고정된 "변경 실패" 메시지만 표시되어 사용자가 실패 원인을 파악하기 어렵습니다. error 객체의 메시지를 활용하여 더 구체적인 정보를 제공하는 것을 고려해보세요.

🔎 개선안
-      {error && <p className="text-sm text-red-600">변경 실패</p>}
+      {error && (
+        <p className="text-sm text-red-600">
+          변경 실패: {error.message || "알 수 없는 오류"}
+        </p>
+      )}
src/app/auth/callback/page.tsx (1)

19-37: useEffect 의존성 배열 정리 권장

현재 의존성 배열에 router가 포함되어 있으나, Next.js의 useRouter 훅이 반환하는 router 객체는 안정적인 참조이므로 의존성에서 제거할 수 있습니다.

또한 next 값이 매 렌더링마다 sp.get()으로 재계산되므로, useMemo로 메모이제이션하는 것을 고려해보세요.

🔎 개선안
+ import { useEffect, useMemo } from "react";
  import { useRouter, useSearchParams } from "next/navigation";
  import { useMe } from "@/src/entities/user/hooks/useMe";
  import { ApiError } from "@/src/shared/api/api-error";

  export default function AuthCallbackPage() {
    const router = useRouter();
    const sp = useSearchParams();
-   const next = sp.get("next") || "/";
+   const next = useMemo(() => sp.get("next") || "/", [sp]);

    const { data: me, isError, error, isSuccess } = useMe();

    useEffect(() => {
      // 리다이렉션 로직...
-   }, [isError, error, isSuccess, me, next, router]);
+   }, [isError, error, isSuccess, me, next]);

    return null;
  }
src/features/onboarding/ui/OnboardingWizard.tsx (2)

22-22: 불필요한 useMemo 제거 필요

draft.nickname ?? ""는 단순한 null 병합 연산이므로 useMemo로 감쌀 필요가 없습니다. 계산 비용이 매우 낮아 메모이제이션의 이점이 없고, 오히려 코드만 복잡하게 만듭니다.

🔎 개선안
-  const defaultNickname = useMemo(() => draft.nickname ?? "", [draft.nickname]);
+  const defaultNickname = draft.nickname ?? "";

31-39: 타입 안정성 개선 필요: as 캐스팅 대신 명시적 검증

Line 32에서 nickname 존재 여부만 검증하고 Line 34에서 as OnboardingPayload로 강제 캐스팅하는 것은 향후 필드가 추가될 때 타입 안정성을 보장하지 못합니다.

현재는 nickname만 필수이므로 문제없지만, 확장성 관점에서 타입 가드 함수를 사용하거나 명시적으로 모든 필수 필드를 검증하는 것이 안전합니다.

🔎 개선안 1: 타입 가드 사용

별도 파일(예: src/entities/user/model/guards.ts)에 타입 가드 추가:

export function isOnboardingPayload(
  payload: Partial<OnboardingPayload>
): payload is OnboardingPayload {
  return typeof payload.nickname === "string" && payload.nickname.trim() !== "";
  // 향후 필드 추가 시 여기에 검증 로직 추가
}

그리고 이 파일에서 사용:

+import { isOnboardingPayload } from "@/src/entities/user/model/guards";

  const submitAll = (payload: Partial<OnboardingPayload>) => {
-    if (!payload.nickname) return;
+    if (!isOnboardingPayload(payload)) return;

-    mutate(payload as OnboardingPayload, {
+    mutate(payload, {
      onSuccess: () => {
        router.replace(next);
      },
    });
  };
🔎 개선안 2: 명시적 객체 생성
  const submitAll = (payload: Partial<OnboardingPayload>) => {
    if (!payload.nickname) return;

-    mutate(payload as OnboardingPayload, {
+    const completePayload: OnboardingPayload = {
+      nickname: payload.nickname,
+      // 향후 필드 추가 시 여기에 명시적으로 추가
+    };
+
+    mutate(completePayload, {
      onSuccess: () => {
        router.replace(next);
      },
    });
  };
src/features/onboarding/ui/steps/NicknameStep.tsx (2)

37-37: 매 렌더링마다 trim() 계산 제거 (파생 상태)

버튼의 disabled 속성에서 !nickname.trim()를 매번 계산하는 대신, 파생 값으로 한 번만 계산하는 것이 좋습니다. 성능 영향은 미미하지만 코드 명확성과 일관성 측면에서 개선할 수 있습니다.

🔎 개선안
  export default function NicknameStep({
    defaultValue = "",
    submitting = false,
    onNext,
  }: Props) {
    const [nickname, setNickname] = useState(defaultValue);
+   const isValid = nickname.trim() !== "";

    const handleSubmit = (e: FormEvent) => {
      e.preventDefault();
      const v = nickname.trim();
      if (!v) return;
      onNext(v);
    };

    return (
      <form onSubmit={handleSubmit}>
        <label>
          닉네임
          <input
            value={nickname}
            onChange={(e) => setNickname(e.target.value)}
            placeholder="닉네임을 입력하세요"
            disabled={submitting}
          />
        </label>

-       <button type="submit" disabled={submitting || !nickname.trim()}>
+       <button type="submit" disabled={submitting || !isValid}>
          다음
        </button>
      </form>
    );
  }

27-35: 접근성 개선: label 구조 권장사항

현재 <label> 안에 텍스트와 <input>을 함께 배치했는데, 명시적으로 htmlForid를 사용하는 것이 접근성 측면에서 더 명확합니다.

🔎 개선안
-      <label>
-        닉네임
-        <input
+      <label htmlFor="nickname-input">닉네임</label>
+      <input
+        id="nickname-input"
          value={nickname}
          onChange={(e) => setNickname(e.target.value)}
          placeholder="닉네임을 입력하세요"
          disabled={submitting}
        />
-      </label>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2a37af and f624d8b.

📒 Files selected for processing (17)
  • src/app/auth/callback/page.tsx
  • src/app/onboarding/page.tsx
  • src/app/test/page.tsx
  • src/entities/user/api/me.ts
  • src/entities/user/api/onboarding.ts
  • src/entities/user/hooks/useCompleteOnboarding.ts
  • src/entities/user/model/types.ts
  • src/features/auth/login-with-google/api/login.ts
  • src/features/auth/logout/api/logout.ts
  • src/features/auth/logout/model/useLogout.ts
  • src/features/onboarding/ui/OnboardingWizard.tsx
  • src/features/onboarding/ui/steps/NicknameStep.tsx
  • src/features/user-profile/api/updateMe.ts
  • src/features/user-profile/hooks/useUpdateMe.ts
  • src/features/user-profile/model/types.ts
  • src/features/user-profile/ui/NicknameEditForm.tsx
  • src/shared/api/api-error.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*

⚙️ CodeRabbit configuration file

**/*: - 리뷰는 한국어로 작성.

  • 유지보수/확장성 관점에서 모듈 경계(의존성 방향, 책임 분리)가 적절한지 최우선으로 확인.
  • 파일/함수 책임이 과도하면 응집도/결합도 기준으로 분리/통합 개선안을 제시.
  • TypeScript에서 any/과도한 as 캐스팅을 지양하고, 도메인 타입(예: ProfileId 같은 ID 타입)으로 의미를 드러내는지 확인.
  • React에서 상태 위치 미스, 불필요한 리렌더링, derived state 남발, useEffect 남용을 지적하고 개선안을 제시.

Files:

  • src/features/user-profile/hooks/useUpdateMe.ts
  • src/features/user-profile/model/types.ts
  • src/entities/user/hooks/useCompleteOnboarding.ts
  • src/features/onboarding/ui/OnboardingWizard.tsx
  • src/features/auth/login-with-google/api/login.ts
  • src/entities/user/api/onboarding.ts
  • src/shared/api/api-error.ts
  • src/features/user-profile/api/updateMe.ts
  • src/features/onboarding/ui/steps/NicknameStep.tsx
  • src/app/auth/callback/page.tsx
  • src/entities/user/api/me.ts
  • src/entities/user/model/types.ts
  • src/app/test/page.tsx
  • src/features/auth/logout/api/logout.ts
  • src/app/onboarding/page.tsx
  • src/features/user-profile/ui/NicknameEditForm.tsx
  • src/features/auth/logout/model/useLogout.ts
**/ui/**/*.tsx

⚙️ CodeRabbit configuration file

**/ui/**/*.tsx: - 상태 위치가 적절한지(상/하위 이동 필요성) 지적하고 개선안 제시.

  • 파생 값은 state로 두지 말고 계산(useMemo는 필요할 때만).
  • 리스트 key 안정성, 불필요한 re-render 유발 props(익명 함수/객체) 지적.

Files:

  • src/features/onboarding/ui/OnboardingWizard.tsx
  • src/features/onboarding/ui/steps/NicknameStep.tsx
  • src/features/user-profile/ui/NicknameEditForm.tsx
**/api/**/*.ts*

⚙️ CodeRabbit configuration file

**/api/**/*.ts*: - API 모듈은 DTO 타입 + 네트워크 호출 + toDomain 매핑까지 책임지는 구조를 우선 권장.

  • UI에서 raw DTO(res.json())를 직접 다루지 않도록, 반환 타입은 Domain(User 등)으로 정규화.
  • fetch 옵션(credentials, headers 등) 규약이 일관적인지 확인.

Files:

  • src/features/auth/login-with-google/api/login.ts
  • src/entities/user/api/onboarding.ts
  • src/shared/api/api-error.ts
  • src/features/user-profile/api/updateMe.ts
  • src/entities/user/api/me.ts
  • src/features/auth/logout/api/logout.ts
src/app/**/*.ts*

⚙️ CodeRabbit configuration file

src/app/**/*.ts*: - Next.js App Router 규칙 준수: server/client 컴포넌트 경계가 적절한지 확인.

  • 가능하면 데이터 패칭은 async 서버 컴포넌트/route handler에서 처리하고, 클라에서 불필요한 fetch/useEffect를 줄일 것.
  • "use client"는 꼭 필요한 곳에만.

Files:

  • src/app/auth/callback/page.tsx
  • src/app/test/page.tsx
  • src/app/onboarding/page.tsx
🧬 Code graph analysis (13)
src/features/user-profile/hooks/useUpdateMe.ts (2)
src/features/user-profile/model/types.ts (1)
  • UpdateMePayload (1-4)
src/features/user-profile/api/updateMe.ts (1)
  • updateMe (7-24)
src/entities/user/hooks/useCompleteOnboarding.ts (1)
src/entities/user/api/onboarding.ts (1)
  • completeOnboarding (5-25)
src/features/onboarding/ui/OnboardingWizard.tsx (3)
src/entities/user/model/types.ts (1)
  • OnboardingPayload (7-12)
src/entities/user/hooks/useCompleteOnboarding.ts (1)
  • useCompleteOnboarding (4-13)
src/features/onboarding/ui/steps/NicknameStep.tsx (1)
  • NicknameStep (11-42)
src/features/auth/login-with-google/api/login.ts (1)
src/shared/api/config.ts (1)
  • API_BASE_URL (1-1)
src/entities/user/api/onboarding.ts (3)
src/entities/user/model/types.ts (1)
  • OnboardingPayload (7-12)
src/shared/api/config.ts (1)
  • API_BASE_URL (1-1)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
src/features/user-profile/api/updateMe.ts (4)
src/features/user-profile/model/types.ts (1)
  • UpdateMePayload (1-4)
src/entities/user/model/types.ts (1)
  • User (1-5)
src/shared/api/config.ts (1)
  • API_BASE_URL (1-1)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
src/app/auth/callback/page.tsx (2)
src/entities/user/hooks/useMe.ts (1)
  • useMe (7-14)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
src/entities/user/api/me.ts (1)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
src/app/test/page.tsx (1)
src/features/user-profile/ui/NicknameEditForm.tsx (1)
  • NicknameEditForm (6-38)
src/features/auth/logout/api/logout.ts (2)
src/shared/api/config.ts (1)
  • API_BASE_URL (1-1)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
src/app/onboarding/page.tsx (1)
src/features/onboarding/ui/OnboardingWizard.tsx (1)
  • OnboardingWizard (11-52)
src/features/user-profile/ui/NicknameEditForm.tsx (1)
src/features/user-profile/hooks/useUpdateMe.ts (1)
  • useUpdateMe (8-17)
src/features/auth/logout/model/useLogout.ts (1)
src/features/auth/logout/api/logout.ts (1)
  • logoutApi (4-14)
🔇 Additional comments (11)
src/features/user-profile/model/types.ts (1)

1-4: 타입 정의가 적절합니다.

PATCH 페이로드에 적합하게 모든 필드를 optional로 정의했고, profileImageUrlnull을 허용해 이미지 제거 시나리오를 명시적으로 지원하는 점이 좋습니다. 타입이 model 디렉토리에 분리되어 관심사가 명확히 구분되어 있습니다.

src/app/test/page.tsx (1)

6-6: 테스트 페이지에 적절하게 추가되었습니다.

NicknameEditForm 컴포넌트가 클라이언트 훅을 사용하고 있으며, 이미 "use client" 지시자가 있는 페이지에 추가되어 클라이언트/서버 경계가 적절합니다.

Also applies to: 15-15

src/features/user-profile/hooks/useUpdateMe.ts (1)

8-17: 훅 구현이 React Query 모범 사례를 잘 따르고 있습니다.

  • useMutation을 활용한 표준 패턴
  • 성공 시 "me" 쿼리를 무효화하여 UI 동기화 보장
  • invalidateQueriesawait로 처리해 리페치 완료를 보장
  • 단일 책임 원칙 준수: 훅은 mutation 로직만, API 호출은 별도 레이어에 위임

모듈 경계가 명확하고(hooks → api → model), 의존성 방향도 적절합니다.

src/features/auth/login-with-google/api/login.ts (1)

4-8: 콜백 페이지에서 redirectToGoogleLogin 호출이 없으므로 순환 리디렉션 위험은 없습니다.

현재 페이지 URL을 next 파라미터로 전달하고 인코딩을 올바르게 처리한 것은 적절합니다. 다만 검토 결과, redirectToGoogleLogingoogleLoginButton.tsx의 버튼 클릭 핸들러에서만 사용되며 콜백 페이지(auth/callback/page.tsx)에서는 호출되지 않습니다. 콜백 페이지는 next 파라미터를 받아 router.replace(next)로 리디렉션할 뿐입니다. 따라서 정상적인 코드 흐름에서 순환 리디렉션은 발생하지 않습니다.

다만 next 파라미터가 /auth/callback 자체를 가리킬 수 있는 엣지 케이스는 존재하므로, 필요시 콜백 핸들러에서 리디렉션 대상이 인증 경로가 아닌지 검증하는 것을 고려해볼 수 있습니다.

Likely an incorrect or invalid review comment.

src/features/auth/logout/api/logout.ts (1)

1-14: 변경사항 적절함

함수명을 logoutApi로 변경하여 명확성을 높였고, ApiError를 사용하여 일관된 에러 핸들링을 구현했습니다. 다른 API 함수들과 구조적 일관성도 유지되고 있습니다.

src/entities/user/api/onboarding.ts (1)

6-17: API 호출 옵션이 적절함

credentials: "include", JSON 헤더 설정, PATCH 메서드 사용이 온보딩 API 특성에 적합하며, 다른 API 모듈과의 일관성도 유지되고 있습니다.

src/entities/user/model/types.ts (1)

7-12: 타입 정의가 명확함

OnboardingPayload 타입이 현재 온보딩 흐름의 요구사항을 명확히 표현하고 있으며, 주석 처리된 필드를 통해 향후 확장 가능성도 고려되었습니다.

src/features/user-profile/ui/NicknameEditForm.tsx (1)

6-8: 로컬 상태 관리가 적절함

폼 입력을 위한 로컬 상태 관리는 이 컴포넌트의 책임 범위에 적절하며, 불필요한 상위 상태 끌어올리기를 피하고 있습니다.

src/app/auth/callback/page.tsx (2)

34-36: 방어적 타입 가드가 적절함

isSuccess && me 조건을 통해 타입 안전성을 확보하는 방어적 코드 작성이 적절합니다.


1-1: 클라이언트 컴포넌트 사용이 적절함

인증 콜백 페이지는 동적 리다이렉션 로직과 클라이언트 측 상태 관리가 필요하므로, "use client" 지시어 사용이 적절합니다.

As per coding guidelines, "use client" is used only where necessary.

src/features/onboarding/ui/steps/NicknameStep.tsx (1)

16-16: defaultValue 변경 시 상태 동기화 미지원

현재 구현은 defaultValue를 초기값으로만 사용하며, 부모에서 defaultValue prop이 변경되어도 로컬 상태(nickname)가 업데이트되지 않습니다.

현재 OnboardingWizard의 사용 패턴을 보면 defaultValue는 마운트 시점에만 설정되고 이후 변경되지 않으므로 문제없지만, 향후 확장 시 주의가 필요합니다.

만약 defaultValue가 동적으로 변경될 가능성이 있다면, useEffect로 동기화하거나 상태를 부모로 올려 제어 컴포넌트로 만드는 것을 고려하세요.

현재 사용 패턴에서는 문제없으나, 향후 확장 시 이 동작이 의도된 것인지 확인이 필요합니다.

Comment thread src/app/auth/callback/page.tsx Outdated
Comment thread src/entities/user/api/onboarding.ts Outdated
Comment on lines +5 to +25
export async function completeOnboarding(payload: OnboardingPayload) {
const res = await fetch(
`${API_BASE_URL}/api/v1/user-profiles/me/onboarding`,
{
method: "PATCH",
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);

if (!res.ok) {
const text = await res.text().catch(() => "");
throw new ApiError(res.status, text || res.statusText);
}

return res.json();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

반환 타입 명시 및 Domain 변환 로직 추가 필요

현재 res.json()을 직접 반환하여 반환 타입이 Promise<any>로 추론되며, API 응답 구조가 불명확합니다.

코딩 가이드라인에 따르면 API 모듈은 DTO 타입 정의 + 네트워크 호출 + Domain 변환까지 책임져야 하며, UI에서 raw DTO를 직접 다루지 않아야 합니다.

🔎 개선안
  1. 응답 DTO 타입 정의:
// src/entities/user/api/onboarding.ts 상단에 추가
interface CompleteOnboardingResponse {
  // 서버 응답 구조에 맞게 정의
  // 예시:
  success: boolean;
  // 또는 User 객체 반환 시
  // user: { nickname: string; slug: string; profileImageUrl: string | null; }
}
  1. 반환 타입 명시 및 필요 시 Domain 변환:
-export async function completeOnboarding(payload: OnboardingPayload) {
+export async function completeOnboarding(
+  payload: OnboardingPayload
+): Promise<CompleteOnboardingResponse> {
   const res = await fetch(
     // ...
   );

   if (!res.ok) {
     const text = await res.text().catch(() => "");
     throw new ApiError(res.status, text || res.statusText);
   }

-  return res.json();
+  const data: CompleteOnboardingResponse = await res.json();
+  return data;
   // 또는 Domain 변환이 필요한 경우:
   // return toDomainUser(data.user);
}

As per coding guidelines, API modules should normalize return types to Domain models.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/entities/user/api/onboarding.ts around lines 5 to 25, the function
returns res.json() directly causing Promise<any> and leaking raw DTOs to the UI;
define a response DTO (e.g., CompleteOnboardingResponse) that matches the API,
change the function signature to return Promise<YourDomainUserType> (or a
normalized domain shape), await and type the parsed JSON into the DTO,
map/convert the DTO into the Domain model inside this module (normalizing field
names and optional values like profileImageUrl), and return that Domain object;
keep existing error handling but ensure the typed parsing happens before mapping
so callers get a strongly-typed Domain object instead of any.

Comment thread src/features/auth/logout/model/useLogout.ts
Comment thread src/features/onboarding/ui/OnboardingWizard.tsx
Comment thread src/features/user-profile/api/updateMe.ts Outdated
Comment thread src/features/user-profile/hooks/useUpdateMe.ts Outdated
Comment on lines +10 to +17
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();

const trimmed = nickname.trim();
if (!trimmed) return;

mutate({ nickname: trimmed });
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

mutation 성공 시 입력 필드 초기화 필요

현재 닉네임 변경이 성공해도 입력 필드가 초기화되지 않아, 사용자가 변경 완료 여부를 명확히 인지하기 어렵습니다.

mutation 성공 시 입력 필드를 초기화하여 UX를 개선하는 것을 권장합니다.

🔎 개선안
 export default function NicknameEditForm() {
-  const { mutate, isPending, error } = useUpdateMe();
+  const { mutate, isPending, error, isSuccess } = useUpdateMe();
   const [nickname, setNickname] = useState("");

+  useEffect(() => {
+    if (isSuccess) {
+      setNickname("");
+    }
+  }, [isSuccess]);
+
   const onSubmit = (e: React.FormEvent) => {
     // ...
   };

또는 mutate 호출 시 onSuccess 콜백 사용:

   const onSubmit = (e: React.FormEvent) => {
     e.preventDefault();
     const trimmed = nickname.trim();
     if (!trimmed) return;

-    mutate({ nickname: trimmed });
+    mutate({ nickname: trimmed }, {
+      onSuccess: () => setNickname(""),
+    });
   };
📝 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
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const trimmed = nickname.trim();
if (!trimmed) return;
mutate({ nickname: trimmed });
};
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const trimmed = nickname.trim();
if (!trimmed) return;
mutate({ nickname: trimmed }, {
onSuccess: () => setNickname(""),
});
};
🤖 Prompt for AI Agents
In src/features/user-profile/ui/NicknameEditForm.tsx around lines 10 to 17, the
form does not clear the input after a successful nickname mutation; update the
mutate call to provide an onSuccess handler (or pass an options object) that
resets the local nickname state to an empty string and optionally shows success
feedback, ensuring the input is cleared when the server confirms the change.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 2, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #10

coderabbitai Bot added a commit that referenced this pull request Jan 2, 2026
Docstrings generation was requested by @aryu1217.

* #9 (comment)

The following files were modified:

* `src/app/auth/callback/page.tsx`
* `src/app/onboarding/page.tsx`
* `src/app/test/page.tsx`
* `src/entities/user/api/me.ts`
* `src/entities/user/api/onboarding.ts`
* `src/entities/user/hooks/useCompleteOnboarding.ts`
* `src/features/auth/login-with-google/api/login.ts`
* `src/features/auth/logout/api/logout.ts`
* `src/features/auth/logout/model/useLogout.ts`
* `src/features/onboarding/ui/OnboardingWizard.tsx`
* `src/features/onboarding/ui/steps/NicknameStep.tsx`
* `src/features/user-profile/api/updateMe.ts`
* `src/features/user-profile/hooks/useUpdateMe.ts`
* `src/features/user-profile/ui/NicknameEditForm.tsx`
@aryu1217 aryu1217 changed the title Feat/profile feat(auth,user): 회원 인증/온보딩/프로필 수정 기능 추가 Jan 2, 2026
Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (1)
src/features/user-profile/api/updateMe.ts (1)

22-22: DTO 타입과 toDomain 매핑이 필요합니다.

현재 as User 타입 단언을 사용하여 응답을 직접 캐스팅하고 있는데, 이는 API 모듈 패턴에서 권장하는 구조를 벗어납니다. 코딩 가이드라인에 따르면 "API 모듈은 DTO 타입 + 네트워크 호출 + toDomain 매핑까지 책임지는 구조를 우선 권장"하며, "UI에서 raw DTO(res.json())를 직접 다루지 않도록, 반환 타입은 Domain(User 등)으로 정규화"해야 합니다.

문제점:

  • 런타임 타입 검증 없이 컴파일 타임에만 타입 안전성 보장
  • API 응답 구조 변경 시 오류를 조기에 감지하기 어려움
  • 타입 불일치 에러가 UI 레이어에서 발생하여 디버깅 어려움

코딩 가이드라인 기준

🔎 DTO 타입과 매핑 함수를 추가하는 리팩토링 제안
  import { ApiError } from "@/src/shared/api/api-error";
  import { API_BASE_URL } from "@/src/shared/api/config";
  import type { User } from "@/src/entities/user/model/types";
  import { UpdateMePayload } from "../model/types";
  
+ // API 응답 DTO 타입 정의
+ type UpdateMeResponseDto = {
+   nickname: string;
+   slug: string;
+   profileImageUrl: string | null;
+ };
+ 
+ // DTO를 Domain 타입으로 변환
+ function toUser(dto: UpdateMeResponseDto): User {
+   return {
+     nickname: dto.nickname,
+     slug: dto.slug,
+     profileImageUrl: dto.profileImageUrl,
+   };
+ }
  
  export async function updateMe(payload: UpdateMePayload): Promise<User> {
    const res = await fetch(`${API_BASE_URL}/api/v1/user-profiles/me`, {
      method: "PATCH",
      credentials: "include",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });
  
    if (!res.ok) {
      const text = await res.text().catch(() => "");
      throw new ApiError(res.status, text || res.statusText);
    }
  
-   return (await res.json()) as User;
+   const dto: UpdateMeResponseDto = await res.json();
+   return toUser(dto);
  }

이 구조를 통해:

  • API 응답 형태를 명시적으로 정의
  • 매핑 로직을 한 곳에서 관리
  • 향후 zod 등의 런타임 검증 라이브러리 추가 용이
🧹 Nitpick comments (3)
src/features/user-profile/api/updateMe.ts (1)

17-20: 에러 응답 파싱 개선을 고려해 보세요.

현재는 에러 응답을 텍스트로만 읽고 있는데, 서버가 JSON 형식의 에러 응답을 반환한다면 구조화된 에러 정보를 활용할 수 있습니다.

🔎 JSON 에러 응답 처리 개선안
  if (!res.ok) {
-   const text = await res.text().catch(() => "");
-   throw new ApiError(res.status, text || res.statusText);
+   let message = res.statusText;
+   try {
+     const errorData = await res.json();
+     message = errorData.message || errorData.error || message;
+   } catch {
+     const text = await res.text().catch(() => "");
+     message = text || message;
+   }
+   throw new ApiError(res.status, message);
  }
src/features/onboarding/ui/OnboardingWizard.tsx (2)

22-22: 불필요한 useMemo 제거 권장

단순한 null coalescing 연산에는 useMemo가 불필요합니다. 직접 계산으로도 충분하며, 코드가 더 간결해집니다.

🔎 제안하는 수정안
-  const defaultNickname = useMemo(() => draft.nickname ?? "", [draft.nickname]);
+  const defaultNickname = draft.nickname ?? "";

31-39: 타입 캐스팅 대신 타입 가드 사용 권장

Line 34의 as OnboardingPayload 캐스팅은 타입 안정성을 우회합니다. 현재는 nickname만 체크하지만, 향후 OnboardingPayload에 다른 필수 필드가 추가되면 런타임 에러가 발생할 수 있습니다.

🔎 타입 가드를 사용한 개선안
+ // 파일 상단에 타입 가드 추가
+ function isValidOnboardingPayload(
+   payload: Partial<OnboardingPayload>
+ ): payload is OnboardingPayload {
+   return !!payload.nickname;
+   // 향후 필드 추가 시: && !!payload.otherField
+ }
+
  const submitAll = (payload: Partial<OnboardingPayload>) => {
-   if (!payload.nickname) return;
+   if (!isValidOnboardingPayload(payload)) return;
 
-   mutate(payload as OnboardingPayload, {
+   mutate(payload, {
      onSuccess: () => {
        router.replace(next);
      },
    });
  };

이렇게 하면:

  1. 타입 시스템의 보호를 받으며 확장 가능
  2. 필수 필드 추가 시 타입 가드만 수정하면 됨
  3. as 캐스팅 불필요
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f624d8b and b295cbb.

📒 Files selected for processing (7)
  • src/app/auth/callback/page.tsx
  • src/entities/user/api/onboarding.ts
  • src/entities/user/hooks/useMe.ts
  • src/features/auth/logout/model/useLogout.ts
  • src/features/onboarding/ui/OnboardingWizard.tsx
  • src/features/user-profile/api/updateMe.ts
  • src/features/user-profile/hooks/useUpdateMe.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/features/user-profile/hooks/useUpdateMe.ts
  • src/app/auth/callback/page.tsx
  • src/entities/user/api/onboarding.ts
  • src/features/auth/logout/model/useLogout.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*

⚙️ CodeRabbit configuration file

**/*: - 리뷰는 한국어로 작성.

  • 유지보수/확장성 관점에서 모듈 경계(의존성 방향, 책임 분리)가 적절한지 최우선으로 확인.
  • 파일/함수 책임이 과도하면 응집도/결합도 기준으로 분리/통합 개선안을 제시.
  • TypeScript에서 any/과도한 as 캐스팅을 지양하고, 도메인 타입(예: ProfileId 같은 ID 타입)으로 의미를 드러내는지 확인.
  • React에서 상태 위치 미스, 불필요한 리렌더링, derived state 남발, useEffect 남용을 지적하고 개선안을 제시.

Files:

  • src/features/onboarding/ui/OnboardingWizard.tsx
  • src/features/user-profile/api/updateMe.ts
  • src/entities/user/hooks/useMe.ts
**/ui/**/*.tsx

⚙️ CodeRabbit configuration file

**/ui/**/*.tsx: - 상태 위치가 적절한지(상/하위 이동 필요성) 지적하고 개선안 제시.

  • 파생 값은 state로 두지 말고 계산(useMemo는 필요할 때만).
  • 리스트 key 안정성, 불필요한 re-render 유발 props(익명 함수/객체) 지적.

Files:

  • src/features/onboarding/ui/OnboardingWizard.tsx
**/api/**/*.ts*

⚙️ CodeRabbit configuration file

**/api/**/*.ts*: - API 모듈은 DTO 타입 + 네트워크 호출 + toDomain 매핑까지 책임지는 구조를 우선 권장.

  • UI에서 raw DTO(res.json())를 직접 다루지 않도록, 반환 타입은 Domain(User 등)으로 정규화.
  • fetch 옵션(credentials, headers 등) 규약이 일관적인지 확인.

Files:

  • src/features/user-profile/api/updateMe.ts
🧬 Code graph analysis (2)
src/features/onboarding/ui/OnboardingWizard.tsx (3)
src/entities/user/model/types.ts (1)
  • OnboardingPayload (7-12)
src/entities/user/hooks/useCompleteOnboarding.ts (1)
  • useCompleteOnboarding (4-13)
src/features/onboarding/ui/steps/NicknameStep.tsx (1)
  • NicknameStep (11-42)
src/features/user-profile/api/updateMe.ts (4)
src/features/user-profile/model/types.ts (1)
  • UpdateMePayload (1-4)
src/entities/user/model/types.ts (1)
  • User (1-5)
src/shared/api/config.ts (1)
  • API_BASE_URL (1-1)
src/shared/api/api-error.ts (1)
  • ApiError (1-5)
🔇 Additional comments (5)
src/entities/user/hooks/useMe.ts (1)

11-11: 이미 올바른 패턴이 적용되어 있습니다.

staleTime: 0invalidateQueries 조합은 이미 구현되어 있습니다. 프로필 업데이트(useUpdateMe), 온보딩(useCompleteOnboarding), 로그아웃(useLogout) 시 모두 queryClient.invalidateQueries({ queryKey: ["me"] })를 호출하고 있으므로, 권장되는 React Query 패턴을 따르고 있습니다.

staleTime: 0은 이 invalidateQueries 전략과 함께 사용되어 mutation 성공 후 데이터 신선도를 보장하는 의도적인 설정입니다.

src/features/user-profile/api/updateMe.ts (2)

1-4: 임포트 구조가 적절합니다.

의존성 방향(features → entities → shared)이 올바르고, 필요한 타입과 유틸리티를 명확하게 가져오고 있습니다.


6-15: fetch 옵션 일관성 확인 완료

updateMe.ts의 fetch 옵션은 다른 API 모듈과 일관되게 구현되어 있습니다. credentials: "include"는 모든 인증이 필요한 API 호출에서 정상 사용 중이고, PATCH 메서드로 body를 전송하는 onboarding.ts의 패턴과 동일하게 Accept 및 Content-Type 헤더를 설정하고 있습니다.

다만 src/features/auth/logout/api/logout.ts의 logoutApi()는 다른 API 호출과 달리 headers를 지정하지 않고 있습니다. 일관성을 위해 logout 요청도 최소한 Accept 헤더를 포함하도록 수정하는 것을 권장합니다.

src/features/onboarding/ui/OnboardingWizard.tsx (2)

1-7: LGTM - 적절한 클라이언트 컴포넌트 구성

"use client" 지시어와 필요한 의존성들이 올바르게 구성되어 있습니다.


24-29: LGTM - 클로저 문제 해결됨

이전 리뷰에서 지적된 클로저 이슈가 올바르게 수정되었습니다. updatedDraft를 명시적으로 생성하여 상태 업데이트와 제출에 동일한 값을 사용하고 있습니다.

Comment on lines +31 to +39
const submitAll = (payload: Partial<OnboardingPayload>) => {
if (!payload.nickname) return;

mutate(payload as OnboardingPayload, {
onSuccess: () => {
router.replace(next);
},
});
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# useCompleteOnboarding 훅의 에러 처리 구현 확인
fd -e ts -e tsx | xargs grep -l "useCompleteOnboarding" | head -5

Repository: Queuing-org/front-end

Length of output: 161


🏁 Script executed:

# OnboardingWizard.tsx 파일 전체 확인
cat -n src/features/onboarding/ui/OnboardingWizard.tsx

Repository: Queuing-org/front-end

Length of output: 1865


🏁 Script executed:

# 다른 mutation 사용 패턴에서 에러 처리 방법 확인 (onError 포함)
rg "mutate\(" -A 5 --type tsx src/ | grep -A 5 "onError"

Repository: Queuing-org/front-end

Length of output: 92


🏁 Script executed:

# useCompleteOnboarding 훅 찾기
find src -name "*useCompleteOnboarding*" -o -name "*Complete*" | head -10

Repository: Queuing-org/front-end

Length of output: 113


🏁 Script executed:

# useCompleteOnboarding 훅의 전체 구현 확인
cat -n src/entities/user/hooks/useCompleteOnboarding.ts

Repository: Queuing-org/front-end

Length of output: 514


🏁 Script executed:

# 다른 useMutation 또는 mutation 패턴에서 onError 사용 현황 확인
rg "onError" -A 3 -B 2 src/ --type ts --type tsx | head -50

Repository: Queuing-org/front-end

Length of output: 92


🏁 Script executed:

# 다른 mutation 패턴에서 onError 사용 현황 확인 (타입 옵션 수정)
rg "onError" -A 3 -B 2 src/ | head -80

Repository: Queuing-org/front-end

Length of output: 47


🏁 Script executed:

# completeOnboarding API 함수 구현 확인
fd completeOnboarding -e ts -e tsx

Repository: Queuing-org/front-end

Length of output: 47


🏁 Script executed:

# 다른 mutation 훅에서 에러 처리 방식 확인
rg "useMutation" -A 10 src/ | grep -A 10 "onError\|onSuccess" | head -60

Repository: Queuing-org/front-end

Length of output: 1911


🏁 Script executed:

# mutate return type를 확인하기 위해 useCompleteOnboarding 호출처 확인
rg "const.*mutate.*=.*useCompleteOnboarding" -A 5 src/

Repository: Queuing-org/front-end

Length of output: 590


🏁 Script executed:

# 컴포넌트에서 에러 상태 관리하는 패턴 확인
rg "error.*useState\|const.*error" src/features/ | grep -i "onboarding\|form" | head -20

Repository: Queuing-org/front-end

Length of output: 47


🏁 Script executed:

# completeOnboarding API 함수 확인
find src -path "*api*onboarding*" -type f

Repository: Queuing-org/front-end

Length of output: 100


🏁 Script executed:

# API 함수 구현 확인
cat -n src/entities/user/api/onboarding.ts

Repository: Queuing-org/front-end

Length of output: 1084


🏁 Script executed:

# useCompleteOnboarding 훅에서 반환하는 전체 객체 확인
rg "useMutation" -A 15 src/entities/user/hooks/useCompleteOnboarding.ts

Repository: Queuing-org/front-end

Length of output: 423


🏁 Script executed:

# React Query의 useMutation에서 error 상태를 받을 수 있는지 확인
# 다른 훅에서 error를 명시적으로 처리하는 패턴이 있는지 확인
rg "\.error\|error:" src/ --type ts --type tsx | head -20

Repository: Queuing-org/front-end

Length of output: 92


뮤테이션 및 훅 수준에서 에러 처리 추가 필요

submitAll 함수에서 onError 핸들러가 없을 뿐만 아니라, useCompleteOnboarding 훅 자체도 에러 처리가 완전히 누락되어 있습니다.

API는 completeOnboarding (lines 23-26)에서 ApiError를 throw하지만, 훅에서 이를 처리하거나 노출하지 않아서 온보딩 실패 시 사용자 피드백이 전혀 제공되지 않습니다.

개선안:

  • useCompleteOnboarding 훅에 onError 핸들러를 추가하거나
  • 컴포넌트에서 뮤테이션의 error 상태를 추출하여 에러 메시지를 표시

현재 상황에서 네트워크 오류, 서버 에러 등이 발생해도 사용자는 아무 알림을 받지 못합니다.

🤖 Prompt for AI Agents
In src/features/onboarding/ui/OnboardingWizard.tsx around lines 31 to 39, the
submitAll mutation call lacks error handling and the useCompleteOnboarding hook
does not expose or handle errors, so failures produce no user feedback; update
the hook to forward mutation error state and accept an onError handler (or
return { mutate, error, isError } from the hook) and then add an onError
callback to the mutate options inside submitAll that captures the ApiError, sets
a local component error state (or uses a toast) and prevents silent failure,
ensuring network/server errors are surfaced to the user and optionally log them
for debugging.

@aryu1217 aryu1217 merged commit a72b95c into main Jan 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant