Skip to content

refactor : login 페이지 #241

Merged
manNomi merged 7 commits intosolid-connection:mainfrom
manNomi:refactor/login_page
Aug 11, 2025
Merged

refactor : login 페이지 #241
manNomi merged 7 commits intosolid-connection:mainfrom
manNomi:refactor/login_page

Conversation

@manNomi
Copy link
Copy Markdown
Contributor

@manNomi manNomi commented Aug 11, 2025

관련 이슈

  • resolves: #이슈번호

작업 내용

  • 로그인 페이지 전반적인 리팩토링 진행 -> 뷰로직 분리 작업 , 책임에 따른 함수 분리
  • 로그인 로직모두 리액트 쿼리를 사용하도록 수정
  • 로그인 페이지가 react-hook-form을 사용하도록 수정
  • getUserRoleFromJwt의 반환 타입을 UserRole로 지정
  • 사용자 역할 비교 로직을 upperCase 기반으로 통일
  • authUtil 내부 버그 수정

특이 사항

  • 로그인 로직 일부가 변경되었으므로 QA 시 로그인/권한 관련 테스트 필요

@manNomi manNomi requested a review from wibaek as a code owner August 11, 2025 07:50
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 11, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

  1. 인증 훅 추가: usePostKakaoAuth, usePostAppleAuth, usePostEmailAuth가 새로 추가되어 서버로 OAuth 코드나 이메일/비밀번호를 전송합니다.
  2. 로그인 페이지 리팩터링: react-hook-form과 zod로 폼 검증을 도입하고, 소셜 로그인은 kakaoLogin/appleLogin 유틸로 위임했습니다.
  3. 콜백 페이지 위임: 카카오/애플 콜백 컴포넌트는 자체 호출 대신 각각의 usePost*Auth 훅을 호출하도록 변경되었습니다.
  4. JWT/역할 처리 변경: jwtUtils가 토큰 만료 검사, 만료 시간 디코드 함수, UserRole 기반 반환 등으로 강화되었습니다.
  5. 멘토 영역 조정: 라우팅 강제(로그인 리다이렉트) 사이드 이펙트를 제거하고 JWT 기반 역할 판정으로 대체했습니다.
  6. SVG·컴포넌트 변경: 로그인 관련 SVG 아이콘 3종이 공개 API로 추가되었고, 기존 Apple/Kakao/Email 버튼 컴포넌트 3종은 삭제되었습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested reviewers

  • wibaek

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b5ac01 and f9edea9.

📒 Files selected for processing (1)
  • src/api/auth/client/usePostKakaoAuth.ts (1 hunks)
✨ 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
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: 4

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

75-81: 중복된 함수 제거 필요

getUserRoleFromJwtSync는 이제 getUserRoleFromJwt로 대체되었으므로 제거해야 합니다. 반환 타입이 string | null로 되어있어 새로운 UserRole 타입 시스템과 일치하지 않습니다.

-// Zustand 기반 토큰 유틸리티 함수들
-export const getUserRoleFromJwtSync = (): string | null => {
-  const token = getAccessToken();
-  if (!token) return null;
-
-  const decoded = parseJwt(token);
-  return decoded?.role ?? null;
-};
🧹 Nitpick comments (12)
src/app/mentor/_ui/MentorClient/_ui/MyMentorSection/index.tsx (1)

8-8: React Query 반환값(data) 구조 분해 적용: 사용성 정돈은 좋습니다

    1. useGetMyMentorProfile 훅의 반환을 data 별칭으로 받는 방식은 표준 패턴과 일치합니다.
    1. 다만 myMentorProfile가 undefined일 수 있으므로 MentorCard가 이를 온전히 처리하는지(스켈레톤/플레이스홀더) 확인을 권장합니다.
    1. 배열 렌더링이 아닌 단일 컴포넌트에 key를 부여하는 것은 불필요합니다. 제거하면 경고 가능성을 줄이고 의도를 명확히 할 수 있습니다.

다음과 같이 가벼운 정리도 고려해 보세요.

- <MentorCard key={myMentorProfile?.id} isMine mentor={myMentorProfile} />
+ {/* 데이터 로딩/없음 처리 예시 */}
+ {myMentorProfile ? (
+   <MentorCard isMine mentor={myMentorProfile} />
+ ) : (
+   <div className="h-24 rounded-md bg-gray-100 animate-pulse" />
+ )}
src/api/auth/client/usePostEmailAuth.ts (4)

8-16: 타입 네이밍 컨벤션 정리 제안

    1. Use 접두사는 일반적으로 훅에 쓰입니다. 응답/요청 타입에는 EmailSignInResponse, EmailSignInRequest처럼 도메인 기반 네이밍을 권장합니다.
    1. 파일 내 일관성을 유지하면 가독성과 자동완성 정확도가 개선됩니다.
-interface UsePostEmailSignInResponse {
+interface EmailSignInResponse {
   accessToken: string;
   refreshToken: string;
 }
 
-interface LoginRequest {
+interface EmailSignInRequest {
   email: string;
   password: string;
 }

18-20: 요청/응답 제네릭과 함수 시그니처 일관성

    1. postEmailAuth의 요청/응답 타입을 위의 리네임과 맞춰 주면 타입 추론이 더 명확해집니다.
-const postEmailAuth = ({ email, password }: LoginRequest): Promise<AxiosResponse<UsePostEmailSignInResponse>> =>
+const postEmailAuth = ({ email, password }: EmailSignInRequest): Promise<AxiosResponse<EmailSignInResponse>> =>
   publicAxiosInstance.post("/auth/email/sign-in", { email, password });

21-29: 에러 처리와 후속 액션(프로필 갱신/라우팅) 보강 제안

    1. onError에서 사용자 메시지(토스트/다이얼로그)와 로깅을 추가하면 UX가 좋아집니다.
    1. 로그인 직후 필요한 데이터(me/프로필 등)가 있다면 invalidateQueries나 prefetch를 통해 자연스러운 화면 전환을 유도하는 것도 좋습니다.
    1. 다른 로그인 훅(애플/카카오)과 정책을 맞춰, 성공 시 이동 경로나 후속 동작을 통일하면 유지보수성이 올라갑니다.
 const usePostEmailAuth = () => {
   return useMutation({
     mutationFn: postEmailAuth,
     onSuccess: (data) => {
       const { accessToken } = data.data;
       setAccessToken(accessToken);
     },
+    onError: (error) => {
+      // TODO: 통합 에러 핸들링(토스트/로그)로 교체
+      alert("이메일 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+    },
   });
 };

8-29: 토큰 보관 전략과 refreshToken 처리 정책 정리 필요

    1. 서버가 refreshToken도 내려주고 있으나 현재는 accessToken만 저장하고 있습니다.
    1. 전반적인 토큰 수명/갱신 전략(메모리/로컬/쿠키, 자동 리프레시 주기, 재시도 정책)을 PR 범위에서 합의해 두는 것이 좋습니다.
    1. 특히 XSS 관점에서 토큰을 어디에 저장할지(메모리 vs localStorage vs httpOnly 쿠키)는 중요한 아키텍처 결정 포인트입니다.

원하시면 현재 앱 상태(Zustand/Interceptor/리프레시 엔드포인트)에 맞춘 토큰 전략 가이드를 정리해 드리겠습니다.

src/app/login/_hooks/useInputHandler.ts (2)

1-6: 타입 import 분리로 TS 정확도 향상

    1. React.ChangeEvent를 모듈에서 직접 참조하면 타입 임포트 누락 시 에러가 날 수 있습니다.
    1. import type으로 분리하면 번들 크기를 건드리지 않으면서 타입 안정성이 개선됩니다.
-import { useState } from "react";
+import { useState } from "react";
+import type { ChangeEvent } from "react";

12-20: 핸들러의 참조 안정성 및 미세 최적화

    1. handleEmailChange를 useCallback으로 감싸면 하위 메모 컴포넌트에 전달 시 불필요한 리렌더를 줄일 수 있습니다.
    1. 현재 로직은 간결하고 명확합니다. 필요 시에만 적용해도 좋습니다.
-  const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+  const handleEmailChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
     const value = e.target.value.trim();
     setShowPasswordField(value.length > 0);
-  };
+  }, []);
src/api/auth/client/usePostAppleAuth.ts (1)

37-55: 성공/실패 라우팅 UX 다듬기 및 로깅 보강

    1. 콜백 페이지에서의 네비게이션은 replace를 사용하면 히스토리에 콜백 URL이 남지 않아 뒤로 가기 UX가 개선됩니다.
    1. onError에서 console.error를 추가해 디버깅 가시성을 높이세요.
    1. refreshToken을 사용한다면 저장 정책을 통일(다른 소셜/이메일 로그인과 일관)하는 것이 좋습니다.
   return useMutation({
     mutationFn: postAppleAuth,
     onSuccess: (response) => {
       const { data } = response;

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

1-20: 아이콘 공개 API 추가: 네이밍/그룹 주석 미세 정리 제안

    1. IconEmailIcon은 ‘Icon’이 중복되어 약간冗長합니다. IconEmail 정도로 단순화하면 좋습니다.
    1. // Login page icons 주석은 해당 블록 상단에 배치하면 가독성이 올라갑니다. 기능상 문제는 없습니다.
-import IconEmailIcon from "./email-icon.svg";
+import IconEmail from "./email-icon.svg";
 ...
 export {
   IconPrepare1,
   IconPrepare2,
   IconPrepare3,
   IconSignupProfileImage,
   IconKakaoLogo,
   IconAppleLogo,
-  IconEmailIcon,
+  IconEmail,
 };
 
-// Login page icons
+// Login page icons

기능 추가 자체는 깔끔합니다. 위 네이밍은 선택 사항입니다.

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

51-54: 사용자 경험 개선 제안

alert() 대신 토스트 알림이나 에러 UI 컴포넌트를 사용하는 것이 더 나은 사용자 경험을 제공할 것 같습니다.

-      alert("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+      // toast.error("카카오 로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
+      // 또는 에러 상태를 저장하여 UI에서 표시
       router.push("/login");
src/app/login/LoginContent.tsx (2)

19-22: 비밀번호 보안 강화 제안

최소 6자는 보안상 약할 수 있습니다. 다음을 고려해보세요:

  1. 최소 8자 이상으로 늘리기
  2. 대소문자, 숫자, 특수문자 포함 규칙 추가
-  password: z.string().min(1, "비밀번호를 입력해주세요.").min(6, "비밀번호는 최소 6자 이상이어야 합니다."),
+  password: z.string()
+    .min(1, "비밀번호를 입력해주세요.")
+    .min(8, "비밀번호는 최소 8자 이상이어야 합니다.")
+    .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, "비밀번호는 대소문자와 숫자를 포함해야 합니다."),

47-51: 불필요한 Enter 키 핸들링 제거

HTML 폼은 기본적으로 input 필드에서 Enter 키를 누르면 submit이 되므로, handleKeyDown 함수와 onKeyDown 속성은 불필요합니다.

-  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
-    if (e.key === "Enter") {
-      handleSubmit(onSubmit)();
-    }
-  };

   // input 필드에서도 onKeyDown 제거
   {...register("email", {
     onChange: handleEmailChange,
   })}
-  onKeyDown={handleKeyDown}

   {...register("password")}
-  onKeyDown={handleKeyDown}

Also applies to: 79-79, 94-94

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3e09d04 and 4b5ac01.

⛔ 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 (17)
  • 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/api/mentors/client/queryKey.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/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts (0 hunks)
  • src/app/mentor/_ui/MentorClient/_ui/MyMentorSection/index.tsx (1 hunks)
  • src/app/mentor/_ui/MentorClient/index.tsx (1 hunks)
  • src/utils/authUtils.ts (2 hunks)
  • src/utils/jwtUtils.ts (3 hunks)
💤 Files with no reviewable changes (4)
  • src/app/mentor/_ui/MentorClient/_hooks/useRouterHandler.ts
  • src/app/login/EmailSignUpButton.tsx
  • src/app/login/AppleLoginButton.tsx
  • src/app/login/KakaoLoginButton.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 (2)
src/app/mentor/_ui/MentorClient/index.tsx (1)
src/utils/jwtUtils.ts (1)
  • getUserRoleFromJwt (101-107)
src/utils/jwtUtils.ts (1)
src/lib/zustand/useTokenStore.ts (1)
  • getAccessToken (31-34)
🔇 Additional comments (5)
src/api/mentors/client/queryKey.ts (1)

1-8: QueryKeys 네이밍 변경 영향도 점검 완료

  1. 영향도 검토
    • rg를 통해 queryKeyqueryKeys 패턴을 검색한 결과, 잔존 참조가 전혀 없습니다.
  2. 조치 사항
    • 추가 수정 불필요합니다.
src/app/login/kakao/callback/KakaoLoginCallbackPage.tsx (1)

10-24: 깔끔한 리팩토링입니다! 👍

OAuth 콜백 처리를 전용 훅으로 위임한 것이 좋은 설계입니다. 코드가 훨씬 간결해지고 관심사 분리가 잘 되어있네요.

src/app/mentor/_ui/MentorClient/index.tsx (1)

10-11: 주석 처리된 코드 확인 필요

멘토 관련 컴포넌트들이 주석 처리되어 있는데, 이는 의도적인 것인가요?

  1. 임시로 비활성화한 것이라면 TODO 주석을 추가해주세요
  2. 영구적으로 제거할 예정이라면 코드를 삭제해주세요

현재 멘토일 경우 빈 화면이 렌더링되는 것 같은데, 이것이 의도된 동작인지 확인 부탁드립니다.

Also applies to: 22-24

src/utils/jwtUtils.ts (1)

95-107: UserRole 변환 로직 잘 구현되었습니다! ✅

PR 목표에 명시된 대로 사용자 역할 비교 로직이 upperCase를 사용하도록 표준화되었네요. 타입 안전성도 잘 보장됩니다.

src/app/login/LoginContent.tsx (1)

26-148: 전반적으로 훌륭한 리팩토링입니다! 🎉

변경사항 요약:

  1. 폼 관리 개선: react-hook-form + zod로 견고한 폼 검증 구현
  2. 관심사 분리: 소셜 로그인 로직을 authUtils로 위임
  3. React Query 통합: 이메일 로그인에 mutation 패턴 적용
  4. UX 개선: 로딩 상태 표시 및 실시간 검증 메시지

코드가 훨씬 깔끔하고 유지보수하기 좋아졌네요!

Comment thread src/api/auth/client/usePostKakaoAuth.ts Outdated
Comment on lines +12 to 20
const { mutate: postAppleAuth } = usePostAppleAuth();

useEffect(() => {
const code = searchParams?.get("code");
if (code) {
sendCodeToBackend(code);
postAppleAuth({ code });
}
}, [searchParams]);
}, [searchParams, postAppleAuth]);

Copy link
Copy Markdown

@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

StrictMode 이중 실행 대비: 중복 요청 가드 추가 권장

    1. Next.js 개발 모드의 StrictMode에서 useEffect가 두 번 실행되어 동일 코드로 두 번 mutate될 수 있습니다.
    1. useRef 가드로 단 한 번만 요청을 보내도록 방지하면 안전합니다.
-import usePostAppleAuth from "@/api/auth/client/usePostAppleAuth";
+import usePostAppleAuth from "@/api/auth/client/usePostAppleAuth";
+import { useRef } from "react";

 const AppleLoginCallbackPage = () => {
   const searchParams = useSearchParams();
-  const { mutate: postAppleAuth } = usePostAppleAuth();
+  const { mutate: postAppleAuth } = usePostAppleAuth();
+  const requestedRef = useRef(false);

   useEffect(() => {
     const code = searchParams?.get("code");
-    if (code) {
-      postAppleAuth({ code });
-    }
-  }, [searchParams, postAppleAuth]);
+    if (!code || requestedRef.current) return;
+    requestedRef.current = true;
+    postAppleAuth({ code });
+  }, [searchParams, postAppleAuth]);
📝 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 { mutate: postAppleAuth } = usePostAppleAuth();
useEffect(() => {
const code = searchParams?.get("code");
if (code) {
sendCodeToBackend(code);
postAppleAuth({ code });
}
}, [searchParams]);
}, [searchParams, postAppleAuth]);
import usePostAppleAuth from "@/api/auth/client/usePostAppleAuth";
import { useRef } from "react";
const AppleLoginCallbackPage = () => {
const searchParams = useSearchParams();
const { mutate: postAppleAuth } = usePostAppleAuth();
const requestedRef = useRef(false);
useEffect(() => {
const code = searchParams?.get("code");
if (!code || requestedRef.current) return;
requestedRef.current = true;
postAppleAuth({ code });
}, [searchParams, postAppleAuth]);
// ...rest of component
};
🤖 Prompt for AI Agents
In src/app/login/apple/callback/AppleLoginCallbackPage.tsx around lines 12 to
20, useEffect can run twice in Next.js StrictMode and cause duplicate
postAppleAuth calls; add a useRef boolean guard (e.g., hasSentRef) initialized
false, check it before calling postAppleAuth, set it to true immediately when
sending to prevent a second mutate, and leave it persistent across renders so
the request is only sent once for a given mount.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

개발모드를 위해서 ref를 삽입하는건 손해아닙니까?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@manNomi 좋은 지적이십니다! 맞습니다, StrictMode는 개발 환경에서만 작동하므로 프로덕션을 위한 코드에 useRef 가드를 추가하는 것은 과도할 수 있습니다.

더 실용적인 대안들:

1. React Query의 기본 기능 활용

const { mutate: postAppleAuth, isPending } = usePostAppleAuth();

useEffect(() => {
  const code = searchParams?.get("code");
  if (code && !isPending) {  // isPending으로 중복 방지
    postAppleAuth({ code });
  }
}, [searchParams, postAppleAuth, isPending]);

2. 서버 측에서 idempotency 보장

  • 동일한 Apple auth code로 중복 요청이 와도 서버에서 처리
  • 이미 사용된 code는 거부하거나 동일한 토큰 반환

3. 개발 환경에서만 경고 표시

useEffect(() => {
  const code = searchParams?.get("code");
  if (code) {
    if (process.env.NODE_ENV === 'development') {
      console.log('Apple auth 요청:', code);
    }
    postAppleAuth({ code });
  }
}, [searchParams, postAppleAuth]);

실제로 Apple OAuth code는 일회용이므로 서버에서 이미 중복 요청을 거부할 가능성이 높습니다. 따라서 useRef 가드 없이 현재 코드를 유지하는 것이 더 간결하고 실용적일 수 있습니다.

Comment thread src/utils/authUtils.ts
Comment on lines +25 to +33
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
Copy Markdown

@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

kakaoLogin 안정성 보강 제안

    1. SSR 환경 가드를 추가해 window 접근 오류를 예방하세요.
    1. SDK 미로딩 시 alert 외에 재시도 유도 또는 로그 수집을 추가하면 문제 추적이 쉬워집니다.
    1. TypeScript에서 window.Kakao 타입 미정의라면 전역 선언을 추가해 any 누수를 줄이세요.
-export const kakaoLogin = () => {
-  if (window.Kakao && window.Kakao.Auth) {
+export const kakaoLogin = () => {
+  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를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
   }
 };

추가: 전역 타입 선언 예시(별도 d.ts)

// global.d.ts
declare global {
  interface Window {
    Kakao?: { Auth?: { authorize: (opts: { redirectUri: string }) => void } };
  }
}
export {};
🤖 Prompt for AI Agents
In src/utils/authUtils.ts around lines 25 to 33, the kakaoLogin function
directly accesses window.Kakao (causing SSR/runtime errors), only shows an alert
on SDK missing (no retry or logging), and may leak any through undeclared window
types; fix by adding an SSR guard (return early if typeof window ===
"undefined"), check for window.Kakao and window.Kakao.Auth before calling
authorize, and when SDK is missing replace the bare alert with a retry mechanism
or a user-friendly prompt and emit a console.error/telemetry event for
diagnostics; additionally add or reference a global.d.ts declaration for
Window.Kakao (as per the suggested shape) to avoid any type leaks and ensure
TypeScript knows Kakao.Auth.authorize signature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

해당부분은 반영하지 않겠습니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@manNomi, 네, 알겠습니다. 결정을 존중하겠습니다.

Comment thread src/utils/authUtils.ts
Comment on lines +35 to +56
export 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) {
window.location.href = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
}
} catch (error) {
console.log(error);
}
};
Copy link
Copy Markdown

@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

appleLogin 초기화/예외 처리 강화와 명시 옵션 제안

    1. 환경변수 누락 시 초기화가 실패하므로 사전 검증을 권장합니다.
    1. response_type: "code"를 명시하면 의도가 분명해집니다.
    1. 에러는 console.error로 기록하고, 필요 시 location.replace로 히스토리 오염을 줄일 수 있습니다.
    1. SSR 가드 및 window.AppleID 타입 선언도 함께 권장드립니다.
 export const appleLogin = async () => {
-  if (!window.AppleID || !window.AppleID.auth) {
+  if (typeof window === "undefined" || !window.AppleID || !window.AppleID.auth) {
     alert("Apple SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
     return;
   }
 
+  const clientId = process.env.NEXT_PUBLIC_APPLE_CLIENT_ID;
+  const scope = process.env.NEXT_PUBLIC_APPLE_SCOPE;
+  const webUrl = process.env.NEXT_PUBLIC_WEB_URL;
+  if (!clientId || !scope || !webUrl) {
+    console.error("Apple OAuth 환경변수가 설정되지 않았습니다.");
+    alert("로그인 설정에 문제가 있습니다. 잠시 후 다시 시도해주세요.");
+    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`,
+    clientId,
+    scope,
+    redirectURI: `${webUrl}/login/apple/callback`,
+    response_type: "code",
     usePopup: true,
   });
 
   try {
     const res: appleOAuth2CodeResponse = await window.AppleID.auth.signIn();
     if (res.authorization) {
-      window.location.href = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
+      window.location.replace(`/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`);
     }
   } catch (error) {
-    console.log(error);
+    console.error("Apple 로그인 오류:", error);
+    alert("애플 로그인 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");
   }
 };

전역 타입 선언 예시(별도 d.ts)

declare global {
  interface Window {
    AppleID?: {
      auth?: {
        init: (opts: { clientId: string; scope: string; redirectURI: string; response_type?: "code"; usePopup?: boolean }) => void;
        signIn: () => Promise<{ authorization?: { code: string } }>;
      };
    };
  }
}
export {};
🤖 Prompt for AI Agents
In src/utils/authUtils.ts around lines 35 to 56, the appleLogin init lacks env
validation, explicit response_type, stronger error handling and SSR/type safety;
validate NEXT_PUBLIC_APPLE_CLIENT_ID, NEXT_PUBLIC_APPLE_SCOPE and
NEXT_PUBLIC_WEB_URL before calling window.AppleID.auth.init and bail with a
user-friendly alert if missing; include response_type: "code" in the init
options; replace console.log with console.error in the catch and use
location.replace for redirecting to /login/apple/callback to avoid history
pollution; add an SSR guard (if typeof window === "undefined" return) and add
the suggested global Window AppleID type declaration in a .d.ts file to make
types explicit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

로그인 관련 로직은 기존의 요소에서 큰 변경사항을 만들지 않겠습니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@manNomi, 이해했습니다. 기존 로그인 로직을 안정적으로 유지하시는 방향으로 진행하시는 것이 좋겠네요. 제안드린 개선사항들은 추후 필요시 참고하실 수 있도록 남겨두겠습니다.

( ◡ _ ◡ )


✏️ Learnings added
Learnt from: manNomi
PR: solid-connection/solid-connect-web#241
File: src/utils/authUtils.ts:35-56
Timestamp: 2025-08-11T08:44:01.653Z
Learning: The user (manNomi) prefers to avoid making significant changes to existing login-related logic in the auth utilities to maintain stability and minimize risk.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@manNomi manNomi merged commit 0826746 into solid-connection:main Aug 11, 2025
1 check was pending
@manNomi manNomi deleted the refactor/login_page 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