Skip to content

Conversation

@manNomi
Copy link
Contributor

@manNomi manNomi commented Aug 11, 2025

관련 이슈

  • resolves: #이슈번호

작업 내용

  • 빌드 시 에러를 유발하던 localStorage 사용 코드를 제거했습니다.
  • 클라이언트 전용 로직을 안전하게 처리하도록 수정했습니다.

특이 사항

  • 빌드 시 SSR 환경에서 localStorage가 존재하지 않아 오류가 발생했었습니다.

  • 이번 수정으로 정적 빌드가 정상적으로 완료됩니다.

  • 빌드 전 localStorage 코드 제거를 깜빡하고 커밋하여 오류가 발생했습니다. 죄송합니다.

@manNomi manNomi requested a review from wibaek as a code owner August 11, 2025 06:32
@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Walkthrough

  1. Mentors QueryKeys 개명: src/api/mentors/client/queryKey.ts에서 export enum 이름을 queryKey → QueryKeys로 변경.
  2. MyMentorProfile 바인딩 수정: src/app/mentor/_ui/MentorClient/_ui/MyMentorSection/index.tsx에서 useGetMyMentorProfile()의 반환값을 data로 받아 myMentorProfile로 별칭 지정.
  3. MentorClient 역할 판별 변경: src/app/mentor/_ui/MentorClient/index.tsx에서 useRouter 기반 판단을 제거하고 getUserRoleFromJwt와 UserRole로 isMentor 판별; MentorPageTabs 및 MyMentorSection 임포트/사용부가 주석 처리됨.
  4. JWT 유틸 개선: src/utils/jwtUtils.ts에서 isTokenExpired 타입을 string → string | null로 확장하고 getUserRoleFromJwt가 getAccessToken()을 사용하도록 변경.
  5. useRouterHandler 부작용 제거: src/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts에서 로그인 강제 리다이렉트(useRouter, useEffect) 로직을 삭제하고 역할 판별만 수행하도록 단순화.
  6. 인증 관련 훅 추가: Apple, Kakao, Email 인증용 클라이언트 훅들(usePostAppleAuth, usePostKakaoAuth, usePostEmailAuth)이 src/api/auth/client에 추가됨.
  7. 로그인 UI 및 흐름 변경: src/app/login/LoginContent.tsx가 react-hook-form과 zod로 재작성되고 소셜 로그인 버튼과 인증 흐름이 훅 기반으로 통합됨.
  8. OAuth 콜백 단순화: src/app/login/apple/callback/AppleLoginCallbackPage.tsx와 src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx가 직접 API 호출을 제거하고 새 훅들을 사용하도록 변경됨.
  9. 로그인 버튼 컴포넌트 삭제: src/app/login/AppleLoginButton.tsx, EmailSignUpButton.tsx, KakaoLoginButton.tsx 파일들이 제거됨.
  10. 유틸 및 아이콘 변경: src/utils/authUtils.ts에 kakaoLogin 및 appleLogin 추가, public/svgs/auth/index.ts에 Kakao/Apple/Email 아이콘들 추가.
  11. 입력 핸들러 훅 추가: src/app/login/_hooks/useInputHandler.ts가 추가되어 이메일 입력에 따라 비밀번호 필드 표시를 제어함.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested reviewers

  • wibaek
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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

Documentation and Community

  • 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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 601eff8 and 0ec6109.

📒 Files selected for processing (2)
  • src/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts (0 hunks)
  • src/app/mentor/_ui/MentorClient/index.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/app/mentor/_ui/MentorClient/index.tsx (1)
src/utils/jwtUtils.ts (1)
  • getUserRoleFromJwt (93-99)
🔇 Additional comments (3)
src/app/mentor/_ui/MentorClient/index.tsx (3)

3-4: SSR 안전성 향상: localStorage 의존을 제거한 역할 판별 전환 좋습니다.

  1. 빌드 타임/SSR 환경에서 안전한 jwtUtils 경유 접근으로 리스크를 낮췄습니다.
  2. 클라이언트 컴포넌트 특성에도 불구, 공용 유틸로 일원화한 점이 유지보수에 유리합니다.

10-11: 멘토 전용 UI 전체 주석으로 멘토 사용자에게 빈 화면이 노출됩니다.

  1. 임시 상태라면 간단한 플레이스홀더(예: “멘토 전용 페이지 준비 중”)라도 노출하는 편이 UX에 안전합니다.
  2. 아니면 기능 플래그/조건부 렌더링으로 단계적 활성화를 권장합니다.

아래처럼 최소 안내를 노출하는 방식을 고려해 주세요(참고용):

{isMentor ? (
  <>
    {/* <MentorPageTabs /> */}
    {/* <MyMentorSection /> */}
    <p style={{ padding: 16 }}>멘토 전용 페이지가 준비 중입니다.</p>
  </>
) : (
  // ...
)}

3-4: 레거시 localStorage 사용 잔존 및 getAccessToken SSR 안전성 확인 요청

  1. 레거시 localStorage 사용 현황 탐색

    • src/utils/authUtils.ts: loadAccessToken/loadRefreshToken 래퍼함수로 localStorage 참조
    • src/app/my/MyContent.tsx: accessToken·refreshToken 직접 removeItem 호출
    • src/app/login/**CallbackPage.tsx: saveAccessToken/saveRefreshToken 임포트
    • src/app/mentor/.../useFirstVisit.ts: 방문 여부 기록을 위해 getItem/setItem 사용
    • src/components/login/signup/SignupSurvey.tsx: saveAccessToken/saveRefreshToken 임포트
  2. getAccessToken SSR 안전성 검증

    • getAccessToken은 zustand store(getState())만 조회
    • window, localStorage, document 접근이 없어 SSR 환경에서도 예외 없이 null 반환 가능

위 결과를 바탕으로 이번 PR 목표와 일치하는지, 남은 레거시 localStorage 접근을 언제·어떻게 제거할지 검토 부탁드립니다.

Comment on lines +8 to +9
import { UserRole } from "@/types/mentor";

Copy link

@coderabbitai coderabbitai bot Aug 11, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 정합성 제안: getUserRoleFromJwt를 UserRole | null 반환으로 강화하세요.

  1. 현재 string | null → enum 비교는 런타임 동작은 가능하나 타입 안전성이 떨어집니다.
  2. 유틸의 반환 타입을 UserRole | null로 좁히면 비교·분기 시 오타/케이스 미스매치를 줄일 수 있습니다.

jwtUtils.ts에 다음과 같은 형태로 보완을 제안합니다(파일 외 참고 코드):

// utils/jwtUtils.ts
import { UserRole } from "@/types/mentor";

const toUserRole = (role: unknown): UserRole | null => {
  if (typeof role !== "string") return null;
  const upper = role.toUpperCase();
  return Object.values(UserRole).includes(upper as UserRole) ? (upper as UserRole) : null;
};

export const getUserRoleFromJwt = (): UserRole | null => {
  const token = getAccessToken();
  if (!token) return null;

  const decoded = parseJwt(token);
  return toUserRole(decoded?.role);
};
🤖 Prompt for AI Agents
In src/app/mentor/_ui/MentorClient/index.tsx around lines 8-9, the file imports
UserRole but getUserRoleFromJwt currently returns string | null which weakens
type safety when comparing to the UserRole enum; change the utility so it
returns UserRole | null by adding a safe converter that verifies the decoded
role is a string, normalizes/case-maps it to the enum format, checks inclusion
against Object.values(UserRole), and returns the matched UserRole or null;
update getUserRoleFromJwt to use this converter and adjust any call sites to
expect UserRole | null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영했습니다

Copy link

Choose a reason for hiding this comment

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

@manNomi 감사합니다! UserRole 타입을 명시적으로 반환하도록 개선해주셔서 타입 안전성이 한층 강화되었네요. 좋은 리팩토링입니다! 👍

✨ ฅ(^・ω・^ฅ) ✨


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines +14 to +16
const userRole = getUserRoleFromJwt();
const isMentor = userRole === UserRole.MENTOR;

Copy link

@coderabbitai coderabbitai bot Aug 11, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

역할 문자열 케이스/널 안전성 보완을 권장합니다.

  1. 토큰의 role이 소문자/대문자 혼재 시, enum 비교가 실패할 수 있습니다.
  2. 널 안전 연산자와 대문자 정규화로 비교를 견고하게 만드세요.

아래 변경으로 케이스·널 안전 비교를 적용할 수 있습니다.

-  const userRole = getUserRoleFromJwt();
-  const isMentor = userRole === UserRole.MENTOR;
+  const role = getUserRoleFromJwt();
+  const isMentor = role?.toUpperCase() === UserRole.MENTOR;

추가로, 로그인 전(null)·알 수 없는 역할 처리 시 현재는 멘티 화면으로 폴백됩니다. 의도된 동작인지 확인을 부탁드립니다. 비의도라면 “로그인이 필요합니다” 또는 중립 플레이스홀더를 권장합니다.


getUserRoleFromJwt 함수 반환값과 UserRole enum 정의를 확인해 봐야 할 것 같아요! 아래 스크립트를 실행해 주세요:


🏁 Script executed:

#!/bin/bash
# 1) getUserRoleFromJwt 함수 정의 확인
rg -n "getUserRoleFromJwt" -A5

# 2) UserRole enum 정의 확인
rg -n "enum UserRole" -A10

Length of output: 4173


토큰 역할 비교에 대소문자 및 null 안정성 강화 필요

현재 getUserRoleFromJwt()string | null을 반환하며, UserRole enum은 "MENTOR"/"MENTEE" 대문자만을 지원하므로 토큰에 담긴 역할이 소문자나 혼합 대소문자일 경우 비교에 실패할 수 있습니다.
아래 절차에 따라 코드를 수정하고, 로그인 전(null) 상태의 화면 전환 의도를 함께 검토해주세요.

  1. 역할 문자열 정규화 적용
    • role?.toUpperCase()로 대문자 일관화
  2. null 안전 비교 도입
    • optional chaining(?)으로 null 혹은 undefined 시 에러 방지
  3. 로그인 전·알 수 없는 역할(fallback) 처리 의도 확인
    • 현재 isMentorfalse가 되면 자동으로 멘티 화면으로 전환됩니다.
    • 의도되지 않은 동작이라면 “로그인이 필요합니다” 안내 또는 중립 플레이스홀더 도입을 고려하세요.
-  const userRole = getUserRoleFromJwt();
-  const isMentor = userRole === UserRole.MENTOR;
+  const role = getUserRoleFromJwt();
+  const isMentor = role?.toUpperCase() === UserRole.MENTOR;
📝 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 userRole = getUserRoleFromJwt();
const isMentor = userRole === UserRole.MENTOR;
const role = getUserRoleFromJwt();
const isMentor = role?.toUpperCase() === UserRole.MENTOR;
🤖 Prompt for AI Agents
In src/app/mentor/_ui/MentorClient/index.tsx around lines 14-16, the code
compares the raw role string from getUserRoleFromJwt() directly to
UserRole.MENTOR which is brittle for null/undefined and mixed-case tokens;
update to normalize and null-safe check by using optional chaining and
toUpperCase() on the returned role before comparing (e.g., role?.toUpperCase()
=== UserRole.MENTOR), and adjust the UI flow for null/unknown roles so that a
false is not assumed to mean "mentee" — explicitly handle unauthenticated or
unknown-role states with a login prompt or neutral placeholder if that is the
intended behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

좋은 아이디어 반영했습니다

Copy link

Choose a reason for hiding this comment

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

@manNomi 감사합니다! 역할 비교의 안정성을 높이는 개선사항을 빠르게 적용해 주셔서 좋네요. 이제 대소문자 변형이나 null 상황에서도 안전하게 처리될 것입니다. 👍

✨ (˶ᵔ ᵕ ᵔ˶) ✨


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

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

🔭 Outside diff range comments (1)
src/utils/authUtils.ts (1)

2-2: 1) SSR 안전성: localStorage 의존성 제거와 isAuthenticated 리팩터가 필요해요.

  • 이 PR의 목적과 달리 localStorage 유틸을 아직 import하고 있어 SSR/정적 빌드에서 다시 터질 수 있어요.
  • 토큰은 zustand 스토어(getAccessToken) 또는 HTTP-only 쿠키 기반 리프레시 플로우를 사용하도록 합쳐주세요.
  • 서버 사이드에서 안전하게 동작하도록 window 접근도 방어하면 좋아요.

아래처럼 수정을 제안드립니다.

-import { isTokenExpired } from "./jwtUtils";
-import { loadAccessToken, loadRefreshToken } from "./localStorage";
+import { isTokenExpired } from "./jwtUtils";
+import { getAccessToken } from "@/lib/zustand/useTokenStore";

-export const isAuthenticated = () => {
-  if (!isTokenExpired(loadAccessToken()) || !isTokenExpired(loadRefreshToken())) {
-    return true;
-  }
-  return false;
-};
+export const isAuthenticated = () => {
+  // SSR 안전 가드
+  if (typeof window === "undefined") return false;
+  const accessToken = typeof getAccessToken === "function" ? getAccessToken() : null;
+  return !isTokenExpired(accessToken);
+};

참고: 만약 getAccessToken 유틸이 없다면 useTokenStore.getState().accessToken을 래핑해 export하는 헬퍼를 추가해 주세요. 원한다면 해당 헬퍼도 같이 제안드릴게요.

Also applies to: 4-9

🧹 Nitpick comments (9)
src/app/login/_hooks/useInputHandler.ts (1)

1-1: 1) 불필요한 리렌더 줄이기: 핸들러를 useCallback으로 메모이즈해요.

  • input onChange에 물리는 핸들러는 참조가 고정되면 자식 렌더 비용이 줄어듭니다.
-import { useState } from "react";
+import { useState, useCallback } from "react";

-  const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  const handleEmailChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     const value = e.target.value.trim();
     setShowPasswordField(value.length > 0);
-  };
+  }, []);

Also applies to: 13-16

src/api/auth/client/usePostEmailAuth.ts (2)

18-29: 1) 응답 언래핑과 타입 강화: React Query에서 data만 반환하도록 단순화해요.

  • AxiosResponse 대신 비즈니스 데이터로 동작하면 onSuccess, select 등에서 타입 안정성이 높아집니다.
  • 실패 재시도는 로그인에서 과도할 수 있어 retry: false 옵션을 고려해요.
-const postEmailAuth = ({ email, password }: LoginRequest): Promise<AxiosResponse<UsePostEmailSignInResponse>> =>
-  publicAxiosInstance.post("/auth/email/sign-in", { email, password });
+const postEmailAuth = async ({ email, password }: LoginRequest): Promise<UsePostEmailSignInResponse> => {
+  const { data } = await publicAxiosInstance.post("/auth/email/sign-in", { email, password });
+  return data;
+}

-  return useMutation({
-    mutationFn: postEmailAuth,
-    onSuccess: (data) => {
-      const { accessToken } = data.data;
+  return useMutation<UsePostEmailSignInResponse, unknown, LoginRequest>({
+    mutationFn: postEmailAuth,
+    retry: false,
+    onSuccess: (data) => {
+      const { accessToken } = data;
       setAccessToken(accessToken);
     },
   });

21-29: 2) 기본 흐름은 좋습니다.

  • 훅 구성과 토큰 저장 타이밍은 자연스럽습니다.
  • UI에서 에러 UX를 통일하려면 onError에서 콜백을 받아 처리하게 하거나, 훅 내에서 toasting을 일관 적용하는 것도 고려해 보세요.
src/app/login/apple/callback/AppleLoginCallbackPage.tsx (1)

16-19: 2) 코드 파라미터 없음 처리: 무한 스피너를 피하도록 폴백을 추가하세요.

  • code가 없는 경우 로그인 페이지로 돌려보내거나 에러 메시지를 보여주면 UX가 좋아집니다.
-    if (code) {
-      postAppleAuth({ code });
-    }
+    if (code) {
+      postAppleAuth({ code });
+    } else {
+      // TODO: 전역 토스트로 교체 권장
+      window.location.replace("/login");
+    }
src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx (1)

14-21: code 파라미터가 없을 때 무한 스피너를 피하고 복구 경로를 주세요.

  1. code가 없으면 현재는 CloudSpinnerPage만 계속 노출됩니다.
  2. 사용자 경험을 위해 로그인으로 되돌리거나 오류 안내를 보여 주세요.

선택지:

  • 간단: code가 falsy면 router.replace("/login?error=oauth_missing_code")로 복귀.
  • 대안: 이 페이지는 스피너만 유지하고, 훅 내부(onError)에서 미인증 상태를 감지해 /login으로 보내도록 통일(팀 합의 필요).
src/api/auth/client/usePostKakaoAuth.ts (2)

51-54: 사용하지 않는 매개변수 정리 및 로깅 확장 제안.

  1. onError의 error 변수가 사용되지 않습니다(린트 경고 가능).
  2. 원인 파악을 위해 최소한 콘솔 로깅을 남기거나 변수명을 _error로 변경해 주세요.
-    onError: (error) => {
-      alert("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
-      router.push("/login");
-    },
+    onError: (_error) => {
+      // console.error("Kakao auth error:", _error);
+      alert("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+      router.push("/login");
+    },

37-41: 타입 추론 개선: mutation에서 AxiosResponse 대신 데이터만 다루도록 단순화.

  1. onSuccess에서 불필요한 response 래핑을 벗기고 data만 사용하면 타입과 가독성이 좋아집니다.
  2. useMutation 제네릭과 mutationFn을 아래처럼 정리하면 onSuccess 시그니처도 간결해집니다.
-  return useMutation({
-    mutationFn: postKakaoAuth,
-    onSuccess: (response) => {
-      const { data } = response;
+  return useMutation<
+    RegisteredKakaoAuthReponse | UnregisteredKakaoAuthReponse,
+    unknown,
+    KakaoAuthRequest
+  >({
+    mutationFn: async ({ code }) => (await publicAxiosInstance.post("/auth/kakao", { code })).data,
+    onSuccess: (data) => {
       if (data.isRegistered) {
         // 기존 회원일 시 - 토큰 저장하고 홈으로 이동
         setAccessToken(data.accessToken);
         router.push("/");
       } else {
         // 새로운 회원일 시 - 회원가입 페이지로 이동
-        router.push(`/sign-up?token=${data.signUpToken}`);
+        router.push(`/sign-up?token=${encodeURIComponent(data.signUpToken)}`);
       }
     },
src/app/login/LoginContent.tsx (2)

36-43: UX 강화(선택): 유효성 기반 버튼 활성화와 폼 모드.

  1. 즉각적인 유효성 피드백을 위해 RHF를 onChange 모드로 두고 isValid를 사용하면 좋습니다.
  2. 비밀번호 영역 표시 전에는 제출 버튼을 비활성화하여 불필요한 에러 표시를 줄일 수 있습니다.
   } = useForm<LoginFormData>({
-    resolver: zodResolver(loginSchema),
+    resolver: zodResolver(loginSchema),
+    mode: "onChange",
     defaultValues: {
       email: "",
       password: "",
     },
   });
...
-            <button
+            <button
               type="submit"
-              disabled={isPending}
+              disabled={isPending || !showPasswordField || !isValid}
               className="flex h-11 w-full items-center justify-center rounded-lg bg-primary p-2.5 font-serif text-base font-medium text-white disabled:cursor-not-allowed disabled:opacity-50"
             >

Also applies to: 100-107


74-80: 자동완성 힌트 추가로 로그인 UX 개선.

  1. 브라우저 자동완성 도움을 위해 email/password 필드에 autoComplete 속성을 부여해 주세요.
  2. 접근성과 입력 편의가 향상됩니다.
-            <input
+            <input
               type="email"
               placeholder="이메일"
               className="h-[41px] w-full rounded-lg border px-5 py-3 font-serif text-xs font-normal leading-normal text-k-400 focus:outline-none"
               {...register("email", {
                 onChange: handleEmailChange,
               })}
+              autoComplete="email"
             />
...
-            <input
+            <input
               type="password"
               placeholder="비밀번호"
               className="h-[41px] w-full rounded-lg border px-5 py-3 font-serif text-xs font-normal leading-normal text-k-400 focus:outline-none"
               {...register("password")}
+              autoComplete="current-password"
             />

Also applies to: 90-97

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ec6109 and af8bc63.

⛔ Files ignored due to path filters (3)
  • public/svgs/auth/apple-logo.svg is excluded by !**/*.svg
  • public/svgs/auth/email-icon.svg is excluded by !**/*.svg
  • public/svgs/auth/kakao-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • public/svgs/auth/index.ts (1 hunks)
  • src/api/auth/client/usePostAppleAuth.ts (1 hunks)
  • src/api/auth/client/usePostEmailAuth.ts (1 hunks)
  • src/api/auth/client/usePostKakaoAuth.ts (1 hunks)
  • src/app/login/AppleLoginButton.tsx (0 hunks)
  • src/app/login/EmailSignUpButton.tsx (0 hunks)
  • src/app/login/KakaoLoginButton.tsx (0 hunks)
  • src/app/login/LoginContent.tsx (2 hunks)
  • src/app/login/_hooks/useInputHandler.ts (1 hunks)
  • src/app/login/apple/callback/AppleLoginCallbackPage.tsx (1 hunks)
  • src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx (1 hunks)
  • src/utils/authUtils.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • src/app/login/KakaoLoginButton.tsx
  • src/app/login/AppleLoginButton.tsx
  • src/app/login/EmailSignUpButton.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-06T06:19:56.705Z
Learnt from: manNomi
PR: solid-connection/solid-connect-web#235
File: src/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts:16-17
Timestamp: 2025-08-06T06:19:56.705Z
Learning: manNomi는 useRouterHandler 훅에서 로그인 체크 및 리다이렉트 로직을 useEffect를 통해 관리하여 사이드 이펙트를 적절히 처리하고 있다.

Applied to files:

  • src/app/login/LoginContent.tsx
🧬 Code Graph Analysis (5)
src/utils/authUtils.ts (1)
src/types/auth.ts (1)
  • appleOAuth2CodeResponse (65-70)
src/api/auth/client/usePostKakaoAuth.ts (3)
src/types/auth.ts (2)
  • RegisteredKakaoAuthReponse (36-40)
  • UnregisteredKakaoAuthReponse (42-48)
src/utils/axiosInstance.ts (1)
  • publicAxiosInstance (183-186)
src/lib/zustand/useTokenStore.ts (1)
  • setAccessToken (35-37)
src/api/auth/client/usePostEmailAuth.ts (2)
src/utils/axiosInstance.ts (1)
  • publicAxiosInstance (183-186)
src/lib/zustand/useTokenStore.ts (1)
  • setAccessToken (35-37)
src/app/login/LoginContent.tsx (2)
src/utils/authUtils.ts (2)
  • kakaoLogin (23-31)
  • appleLogin (33-54)
public/svgs/auth/index.ts (3)
  • IconKakaoLogo (14-14)
  • IconEmailIcon (16-16)
  • IconAppleLogo (15-15)
src/api/auth/client/usePostAppleAuth.ts (2)
src/utils/axiosInstance.ts (1)
  • publicAxiosInstance (183-186)
src/lib/zustand/useTokenStore.ts (1)
  • setAccessToken (35-37)
🔇 Additional comments (5)
src/utils/authUtils.ts (1)

26-27: 환경변수 누락 점검 요청

  1. src/utils/authUtils.ts 26–27
    • redirectUri: \${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback``
  2. src/utils/authUtils.ts 40–43
    • clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID
    • scope: process.env.NEXT_PUBLIC_APPLE_SCOPE
    • redirectURI: \${process.env.NEXT_PUBLIC_WEB_URL}/login/apple/callback``

위 위치에서 참조되는 NEXT_PUBLIC_WEB_URL, NEXT_PUBLIC_APPLE_CLIENT_ID, NEXT_PUBLIC_APPLE_SCOPE가 빌드·런타임 환경 모두에서 누락 없이 설정되어 있는지 반드시 확인해 주세요. 누락 시 SDK 동작 또는 리디렉션이 실패할 수 있습니다.

src/api/auth/client/usePostAppleAuth.ts (1)

42-49: 2) 토큰/라우팅 처리 흐름은 적절합니다.

  • 기존 회원/신규 회원 분기와 액세스 토큰 저장 시점이 명확합니다.
  • Kakao 플로우와 일관성 유지도 잘 되어 있어요.
src/api/auth/client/usePostKakaoAuth.ts (1)

1-59: 😎 SSR/정적 빌드 안전성 검증
다음 세 가지 사항을 점검해 주세요:

  1. localStorage 직접 호출 탐색
    • src/app/mentor/.../useFirstVisit.ts, src/app/my/MyContent.tsx, src/components/login/signup/SignupSurvey.tsx 등에서 여전히 localStorage.getItem/setItem/removeItem이 남아 있습니다.
  2. Zustand persist 미사용 확인
    useTokenStorepersist 미들웨어가 적용되지 않아 토큰은 메모리만 사용되지만, src/utils/localStorage.ts의 함수 이름이 혼동을 일으킬 수 있습니다.
  3. 클라이언트 사이드 방어 보강
    • localStorage 접근 구문에 use client 선언을 추가하거나 if (typeof window !== 'undefined') 조건문으로 SSR 시점 오류를 방지해주세요.

위 항목을 반영해 클라이언트 전용 코드에서만 localStorage를 사용하도록 조치 부탁드립니다.

public/svgs/auth/index.ts (1)

1-20: 아이콘 집합 확장 LGTM.

  1. 신규 아이콘 3종 import/export 일관성 좋습니다.
  2. 로그인 페이지에서의 사용 목적 주석도 명확합니다.
src/app/login/LoginContent.tsx (1)

10-17: 🚨 appleLogin 유틸 함수 내 router.push 수정 요청

앗! router 객체 없이 router.push를 호출해서 런타임 오류가 발생할 위험이 있어요. 조심조심~

  1. 라우터 객체 미정의 호출 수정 필요
    └── src/utils/authUtils.ts 49행의 router.pushrouter가 선언/임포트되지 않아 오류를 유발합니다.
  2. 유틸 함수 내 라우팅은 브라우저 내장 API로 대체
    └── window.location.href를 사용해 리다이렉트하도록 변경해주세요.
  3. 또는 라우팅 책임은 컴포넌트/훅으로 위임
    └── 호출부(컴포넌트/커스텀 훅)에서 useRouter를 사용해 라우팅 로직을 처리하세요.

예시 변경안:

-    if (res.authorization) {
-      router.push(`/login/apple/callback?code=${res.authorization.code}`);
-    }
+    if (res.authorization) {
+      window.location.href = `${process.env.NEXT_PUBLIC_WEB_URL}/login/apple/callback?code=${res.authorization.code}`;
+    }

Likely an incorrect or invalid review comment.

Comment on lines 37 to 55
return useMutation({
mutationFn: postAppleAuth,
onSuccess: (response) => {
const { data } = response;

if (data.isRegistered) {
// 기존 회원일 시 - 토큰 저장하고 홈으로 이동
setAccessToken(data.accessToken);
router.push("/");
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: (error) => {
alert("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
router.push("/login");
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

1) 재시도/중복 처리: 로그인 실패 재시도와 다중 알림을 막아요.

  • React Query 기본 retry(3)로 인해 동일 오류에 3번 alert가 뜰 수 있어요.
  • callback 페이지의 Strict Mode 이펙트 중복 호출과 겹치면 서버에 이중 요청이 갑니다.
-  return useMutation({
+  return useMutation<AxiosResponse<AppleAuthResponse>, unknown, AppleAuthRequest>({
     mutationFn: postAppleAuth,
+    retry: false,
     onSuccess: (response) => {
       const { data } = response;
@@
     onError: (error) => {
-      alert("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+      // TODO: 전역 토스트로 교체 권장
+      alert("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
       router.push("/login");
     },
   });
📝 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 useMutation({
mutationFn: postAppleAuth,
onSuccess: (response) => {
const { data } = response;
if (data.isRegistered) {
// 기존 회원일 시 - 토큰 저장하고 홈으로 이동
setAccessToken(data.accessToken);
router.push("/");
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: (error) => {
alert("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
router.push("/login");
},
});
return useMutation<AxiosResponse<AppleAuthResponse>, unknown, AppleAuthRequest>({
mutationFn: postAppleAuth,
retry: false,
onSuccess: (response) => {
const { data } = response;
if (data.isRegistered) {
// 기존 회원일 시 - 토큰 저장하고 홈으로 이동
setAccessToken(data.accessToken);
router.push("/");
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
}
},
onError: (error) => {
// TODO: 전역 토스트로 교체 권장
alert("애플 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
router.push("/login");
},
});
🤖 Prompt for AI Agents
src/api/auth/client/usePostAppleAuth.ts around lines 37 to 55: the mutation
currently uses React Query default retry which can produce repeated alerts and
duplicate server requests under Strict Mode; set retry: 0 (or false) in the
useMutation options to disable automatic retries, and add a simple
duplicate-handling guard for the onError alert (e.g., maintain a local ref/flag
that ensures the alert and router.push("/login") run only once per mutation) or
prevent duplicate mutate calls at the caller by checking isLoading before
calling mutate.

Comment on lines 11 to 23
interface RegisteredKakaoAuthReponse {
isRegistered: true;
accessToken: string;
refreshToken: string;
}

interface UnregisteredKakaoAuthReponse {
isRegistered: false;
nickname: string;
email: string;
profileImageUrl: string;
signUpToken: string;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복 타입 선언 제거: 공용 타입을 import해서 DRY를 지켜요.

  1. 같은 인터페이스가 src/types/auth.ts에도 존재합니다.
  2. 타입을 재정의하면 불일치 위험이 커져 유지보수가 어려워집니다.
+import { RegisteredKakaoAuthReponse, UnregisteredKakaoAuthReponse } from "@/types/auth";
 
-// Kakao
-interface RegisteredKakaoAuthReponse {
-  isRegistered: true;
-  accessToken: string;
-  refreshToken: string;
-}
-
-interface UnregisteredKakaoAuthReponse {
-  isRegistered: false;
-  nickname: string;
-  email: string;
-  profileImageUrl: string;
-  signUpToken: string;
-}
📝 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
interface RegisteredKakaoAuthReponse {
isRegistered: true;
accessToken: string;
refreshToken: string;
}
interface UnregisteredKakaoAuthReponse {
isRegistered: false;
nickname: string;
email: string;
profileImageUrl: string;
signUpToken: string;
}
import { RegisteredKakaoAuthReponse, UnregisteredKakaoAuthReponse } from "@/types/auth";
🤖 Prompt for AI Agents
In src/api/auth/client/usePostKakaoAuth.ts around lines 11 to 23, you have
duplicate local interfaces (RegisteredKakaoAuthReponse and
UnregisteredKakaoAuthReponse) that already exist in src/types/auth.ts; remove
these local type definitions and import the shared types from src/types/auth.ts
instead, updating the import statement and any references to use the exact
exported type names (fixing any spelling mismatches) so the file relies on the
single source of truth and avoids drift.

Comment on lines 46 to 49
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

쿼리 인코딩: signUpToken을 URL에 안전하게 전달해요.

  1. signUpToken에 예약문자가 포함되면 링크가 깨질 수 있습니다.
  2. encodeURIComponent로 방지하세요.
-        router.push(`/sign-up?token=${data.signUpToken}`);
+        router.push(`/sign-up?token=${encodeURIComponent(data.signUpToken)}`);
📝 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
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${data.signUpToken}`);
}
} else {
// 새로운 회원일 시 - 회원가입 페이지로 이동
router.push(`/sign-up?token=${encodeURIComponent(data.signUpToken)}`);
}
🤖 Prompt for AI Agents
In src/api/auth/client/usePostKakaoAuth.ts around lines 46 to 49, the code
appends data.signUpToken directly into the query string which can break the URL
if the token contains reserved characters; update the router.push call to
URL-encode the token by passing encodeURIComponent(data.signUpToken) (and
defensively ensure data.signUpToken is defined before encoding) so the token is
transmitted safely in the query parameter.

Comment on lines 14 to 28
useEffect(() => {
const code = searchParams?.get("code");
if (code) {
sendCodeToBackend(code);
postAppleAuth({ code });
}
}, [searchParams]);
}, [searchParams, postAppleAuth]);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

1) 이펙트 중복 호출 가드: Strict Mode에서 중복 전송을 막아요.

  • 개발 환경에서 useEffect가 두 번 실행되어 동일 코드가 두 번 전송될 수 있어요.
  • useRef 가드로 한 번만 호출되도록 해주세요.
-import { useEffect } from "react";
+import { useEffect, useRef } from "react";
@@
   const { mutate: postAppleAuth } = usePostAppleAuth();
+  const calledRef = useRef(false);
@@
-  useEffect(() => {
+  useEffect(() => {
     const code = searchParams?.get("code");
-    if (code) {
-      postAppleAuth({ code });
+    if (!calledRef.current && code) {
+      calledRef.current = true;
+      postAppleAuth({ code });
     }
-  }, [searchParams, postAppleAuth]);
+  }, [searchParams, postAppleAuth]);
🤖 Prompt for AI Agents
In src/app/login/apple/callback/AppleLoginCallbackPage.tsx around lines 14 to
20, the useEffect may run twice in React Strict Mode and cause duplicate
postAppleAuth calls; add a useRef boolean (e.g., calledRef) initialized to
false, check it inside the effect and only call postAppleAuth when
calledRef.current is false, then set calledRef.current = true immediately after
calling; ensure the ref is used in the dependency-free effect (or keep current
dependencies) so the postAppleAuth is executed only once during the component
lifecycle in development and production.

Comment on lines 14 to 30
useEffect(() => {
const code = searchParams?.get("code");
if (code) {
sendCodeToBackend(code);
postKakaoAuth({ code });
}
}, [searchParams]);
}, [searchParams, postKakaoAuth]);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복 호출 방지: StrictMode/의존성 갱신으로 이중 로그인 요청이 발생할 수 있어요.

  1. useEffect는 React StrictMode와 searchParams 객체 갱신으로 두 번 이상 실행될 수 있어, 같은 code에 대해 postKakaoAuth가 중복 호출될 수 있습니다.
  2. code 단위로 한 번만 호출되도록 ref 가드를 추가해 주세요.

적용 예시:

-import { useEffect } from "react";
+import { useEffect, useRef } from "react";

 const KakaoLoginCallbackPage = () => {
   const searchParams = useSearchParams();
-  const { mutate: postKakaoAuth } = usePostKakaoAuth();
+  const { mutate: postKakaoAuth } = usePostKakaoAuth();
+  const processedCodeRef = useRef<string | null>(null);

   useEffect(() => {
     const code = searchParams?.get("code");
-    if (code) {
-      postKakaoAuth({ code });
-    }
+    if (code && processedCodeRef.current !== code) {
+      processedCodeRef.current = code;
+      postKakaoAuth({ code });
+    }
   }, [searchParams, postKakaoAuth]);
🤖 Prompt for AI Agents
In src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx around lines 14 to
20, the useEffect can call postKakaoAuth multiple times (React StrictMode or
searchParams updates) causing duplicate login requests; add a ref-based guard
that tracks which codes have been processed (e.g., a useRef storing last
processed code or a Set of processed codes), check the ref before calling
postKakaoAuth and only call when the code is present and not yet processed, then
mark the code as processed in the ref immediately after initiating the call so
the effect cannot trigger a duplicate request for the same code.

Comment on lines 48 to 83
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleEmailLogin();
}
};

const kakaoLogin = () => {
if (window.Kakao && window.Kakao.Auth) {
window.Kakao.Auth.authorize({
redirectUri: `${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback`,
});
} else {
alert("Kakao SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
}
};

const appleLogin = async () => {
if (!window.AppleID || !window.AppleID.auth) {
alert("Apple SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
return;
}

window.AppleID.auth.init({
clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID,
scope: process.env.NEXT_PUBLIC_APPLE_SCOPE,
redirectURI: `${process.env.NEXT_PUBLIC_WEB_URL}/login/apple/callback`,
usePopup: true,
});

try {
const res: appleOAuth2CodeResponse = await window.AppleID.auth.signIn();
if (res.authorization) {
router.push(`/login/apple/callback?code=${res.authorization.code}`);
}
} catch (error) {
console.log(error);
}
};

const handleEmailLogin = async () => {
if (!email.trim()) {
alert("이메일을 입력해주세요.");
return;
}
if (!password) {
alert("비밀번호를 입력해주세요.");
return;
}
try {
const response = await emailAuthApi(email, password);
saveAccessToken(response.data.accessToken);
saveRefreshToken(response.data.refreshToken);
router.push("/");
} catch (error: any) {
console.error(error);
alert(error.response?.data?.message || "로그인에 실패했습니다.");
handleSubmit(onSubmit)();
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복 제출 방지: onKeyDown + form onSubmit 동시 사용은 이중 요청을 유발할 수 있어요.

  1. 입력 칸에서 Enter 시 keydown 핸들러와 브라우저 기본 submit이 모두 실행되어 postEmailAuth가 두 번 불릴 가능성이 있습니다.
  2. RHF의 만으로 Enter 제출이 동작하므로 onKeyDown을 제거해 주세요.
-  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
-    if (e.key === "Enter") {
-      handleSubmit(onSubmit)();
-    }
-  };
...
-              onKeyDown={handleKeyDown}
+              // Enter 제출은 form onSubmit으로 일원화
...
-              onKeyDown={handleKeyDown}
+              // Enter 제출은 form onSubmit으로 일원화

Also applies to: 80-81, 95-96

🤖 Prompt for AI Agents
In src/app/login/LoginContent.tsx around lines 48-52 (and similarly at 80-81 and
95-96), remove the onKeyDown Enter handler from the input elements because it
can cause duplicate submissions together with the form's onSubmit; rely solely
on RHF's <form onSubmit={handleSubmit(onSubmit)}> for Enter-to-submit behavior,
and ensure any lingering onKeyDown props are deleted or not passed so
postEmailAuth is only invoked once.

Comment on lines 23 to 31
export const kakaoLogin = () => {
if (window.Kakao && window.Kakao.Auth) {
window.Kakao.Auth.authorize({
redirectUri: `${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback`,
});
} else {
alert("Kakao SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

4) SSR 가드 추가: window/alert 접근 전에 typeof window 체크가 필요해요.

  • SSR 환경에서 함수가 호출되면 window/alert 접근으로 크래시할 수 있어요.
  • 간단한 가드로 서버에서 no-op 처리하면 안전합니다.
 export const kakaoLogin = () => {
-  if (window.Kakao && window.Kakao.Auth) {
+  if (typeof window === "undefined") return;
+  if (window.Kakao && window.Kakao.Auth) {
     window.Kakao.Auth.authorize({
       redirectUri: `${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback`,
     });
   } else {
     alert("Kakao SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
   }
 };

 export const appleLogin = async () => {
-  if (!window.AppleID || !window.AppleID.auth) {
+  if (typeof window === "undefined") return;
+  if (!window.AppleID || !window.AppleID.auth) {
     alert("Apple SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
     return;
   }

덧: window.Kakao / window.AppleID는 전역 타입 선언이 없으면 TS 경고가 날 수 있어요. 전역 타입에 아래를 추가하는 것도 추천합니다.

  • declare global interface Window { Kakao?: any; AppleID?: any; }
    필요하시면 global.d.ts 패치도 제안드릴게요.

Also applies to: 33-54

🤖 Prompt for AI Agents
In src/utils/authUtils.ts around lines 23 to 31 (and also apply same change to
33-54): current code directly accesses window and alert which will crash under
SSR; add a typeof window !== "undefined" guard (and check typeof
window.Kakao/AppleID) so the function becomes a no-op on the server, and use a
safe client-only fallback that calls alert only when window is defined. Also add
(or recommend) a global type declaration like declare global interface Window {
Kakao?: any; AppleID?: any; } in global.d.ts to remove TypeScript warnings.

});

try {
const res: appleOAuth2CodeResponse = await window.AppleID.auth.signIn();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

2) 타입 누락: appleOAuth2CodeResponse import가 필요해요.

  • 현재 파일에서 appleOAuth2CodeResponse를 사용하지만 import가 없어서 TS 에러가 납니다.

아래 한 줄을 상단에 추가해 주세요.

+import type { appleOAuth2CodeResponse } from "@/types/auth";
📝 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 res: appleOAuth2CodeResponse = await window.AppleID.auth.signIn();
// at the top of src/utils/authUtils.ts
import type { appleOAuth2CodeResponse } from "@/types/auth";
🤖 Prompt for AI Agents
In src/utils/authUtils.ts around line 47, the type appleOAuth2CodeResponse is
used but not imported; add a top-level import for it such as: import type {
appleOAuth2CodeResponse } from '<the-apple-signin-library-your-project-uses>';
placing it with the other imports and using the correct module path that exports
that type.

Comment on lines 49 to 50
router.push(`/login/apple/callback?code=${res.authorization.code}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

3) router 미정의 사용: 유틸 파일에서 router.push는 동작하지 않아요.

  • 해당 파일은 훅/컴포넌트가 아니어서 useRouter를 쓸 수 없고, 지금은 router 변수가 정의되어 있지 않아 런타임 에러가 납니다.
  • 유틸에서는 window.location으로 내비게이션하는 편이 안전합니다.

아래처럼 교체해 주세요.

-      router.push(`/login/apple/callback?code=${res.authorization.code}`);
+      window.location.href = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
📝 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
router.push(`/login/apple/callback?code=${res.authorization.code}`);
}
window.location.href = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
}
🤖 Prompt for AI Agents
In src/utils/authUtils.ts around lines 49-50, router is undefined in this
non-component utility and router.push causes a runtime error; replace the
router-based navigation with a window.location navigation (e.g., use
window.location.href or window.location.assign with the same
/login/apple/callback URL including res.authorization.code) so the utility
performs a full-page redirect safely.

@manNomi manNomi merged commit 46a8edc into solid-connection:main Aug 11, 2025
1 check passed
@manNomi manNomi deleted the fix/login branch December 11, 2025 07:44
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