Skip to content

Conversation

@yuseoyeon
Copy link
Collaborator

@yuseoyeon yuseoyeon commented Aug 19, 2025

💻 관련 이슈

💡 작업내용

  • 네이버, 구글 로그인 연동
  • 비로그인 시 로그인 모달 띄우기 설정

🧐 참고 사항

🖼️ 스크린샷 or 실행영상

2026-01-09.140556.mp4

️✅ 체크리스트 (PR 올리기 전 아래 내용을 확인해 주세요)

  • Reviewers를 지정해주세요
  • Assignees는 본인을 선택해주세요
  • label을 선택해주세요
  • Merge 하려는 PR 및 Commit들을 로컬에서 실행했을 때 에러가 발생하지 않았나요?

@yuseoyeon yuseoyeon self-assigned this Aug 19, 2025
@yuseoyeon yuseoyeon linked an issue Aug 19, 2025 that may be closed by this pull request
2 tasks
@coderabbitai
Copy link

coderabbitai bot commented Aug 19, 2025

📝 Walkthrough

Walkthrough

네이버 및 구글 OAuth 소셜 로그인을 지원하도록 시스템을 확장했습니다. 새로운 로그인 제공자 API를 추가하고, 기존 Kakao 로직을 일반화하며, 모달 기반 로그인 흐름으로 전환했습니다. 팟 필터링 파라미터도 recruitmentRole에서 recruitmentRoles로 이름 변경했습니다.

Changes

Cohort / File(s) 변경 요약
API 및 타입 정의
src/apis/userAPI.ts, src/apis/potAPI.ts, src/apis/types/pot.ts
새로운 구글 및 네이버 로그인 API 함수 추가; 팟 필터링 파라미터를 recruitmentRole에서 recruitmentRoles로 일관되게 이름 변경
Hooks
src/apis/hooks/users/useGetSignIn.ts, src/apis/hooks/pots/useGetPots.ts
useGetSignInsignInType 파라미터를 받아 switch 기반 로그인 제공자 선택; useGetPots의 파라미터를 recruitmentRoles로 업데이트
모달 및 로그인 컴포넌트
src/components/commons/Modal/LoginModal/LoginModal.tsx, src/components/cards/CtaCard/CtaCard.tsx, src/pages/Home/Home.tsx, src/components/layouts/SideBar/SideBar.tsx, src/pages/Home/components/Feed/Feed.tsx
LoginModal이 환경 변수로부터 구글/네이버 OAuth URL 구성; 여러 컴포넌트가 직접 OAuth 리다이렉트 대신 모달 기반 로그인 흐름으로 전환; 접근 토큰 검증 추가
팟 페이지
src/pages/Pots/PotMain.tsx, src/pages/Pots/pages/AllPot/AllPotPage.tsx
인증되지 않은 사용자를 위한 접근 토큰 가드 추가; recruitmentRoles 파라미터 사용
라우팅 및 기타
src/routes/router.tsx, src/pages/Callback/Callback.tsx, src/pages/SignUp/components/CompleteModal/CompleteModal.tsx
Callback 경로에 :loginType 동적 세그먼트 추가; Callback 컴포넌트에서 URL 파라미터로 로그인 타입 읽음; CompleteModal의 취소 핸들러 업데이트

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as React App
    participant Modal as LoginModal
    participant OAuth as OAuth Provider<br/>(Google/Naver/Kakao)
    participant CallbackPage as Callback Page
    participant Server as API Server
    participant AppHome as Home/App

    User->>App: 로그인 필요한 액션 실행
    App->>App: 접근 토큰 확인
    alt No Token
        App->>Modal: LoginModal 열기
        User->>Modal: OAuth 제공자 선택
        Modal->>OAuth: OAuth 인증 URL로 리다이렉트
        User->>OAuth: 인증 진행
        OAuth-->>CallbackPage: code와 함께 리다이렉트<br/>(loginType 포함)
    end
    CallbackPage->>CallbackPage: useParams에서 loginType 추출
    CallbackPage->>CallbackPage: useGetSignIn(loginType) 호출
    CallbackPage->>Server: signInType 기반 로그인<br/>(Google/Naver/Kakao)
    Server-->>CallbackPage: tokenServiceResponse 반환
    CallbackPage->>AppHome: 토큰 저장 및 네비게이션
    AppHome->>User: 홈 또는 회원가입 화면 표시
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • durumi99
  • Sohyunnnn
🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning recruitmentRole → recruitmentRoles 파라미터 이름 변경은 요청된 범위를 벗어나며, 별도의 기능 구현이지만 로그인 기능과 무관한 변경입니다. 파라미터 이름 변경(recruitmentRole → recruitmentRoles)은 별도의 이슈로 분리하거나, 이슈 #280의 범위에 명시적으로 포함되어야 합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명이 기본 구조는 따르고 있으나 참고 사항과 체크리스트가 불완전함. 참고 사항 섹션을 더 자세히 작성하고(로컬 테스트 에러 관련 설명 추가), 체크리스트 항목들을 모두 확인 후 체크하거나 미확인 사유를 명시해주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 요약하고 있습니다. 네이버·구글 로그인 연동과 로그인 모달 설정이라는 핵심 기능을 간결하고 구체적으로 표현합니다.
Linked Issues check ✅ Passed 모든 코드 변경 사항이 연결된 이슈 #280의 목표를 충족합니다. 네이버 로그인(userAPI.ts, useGetSignIn.ts, Callback.tsx, LoginModal.tsx 등), 구글 로그인(동일 파일들), 그리고 비로그인 상태 처리(여러 컴포넌트에서 모달 추가)가 완벽히 구현되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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
Collaborator

@Sohyunnnn Sohyunnnn left a comment

Choose a reason for hiding this comment

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

로컬 안되면 백엔드한테 문의 해봐야 할 거 같아요

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/SignUp/components/CompleteModal/CompleteModal.tsx (1)

13-17: 회원가입 완료 모달의 동작 변경이 적절합니다.

취소와 확인 버튼이 모두 홈으로 이동하도록 변경한 것은 회원가입 완료 시나리오에서 합리적인 UX입니다. 다만 onModalCancel prop이 CompleteModal 컴포넌트에서 사용되지 않고 있으므로 제거를 권장합니다.

제거 대상:

  • CompleteModal.tsx 라인 13-17: onModalCancel prop 선언 및 함수 파라미터 제거
  • SignUp.tsx 라인 119: CompleteModal 호출 시 onModalCancel={handleModalCancel} 전달 제거
♻️ 제안 변경사항

CompleteModal.tsx:

 interface CompleteModalProps {
-  onModalCancel: () => void;
 }

-function CompleteModal({ onModalCancel }: CompleteModalProps) {
+function CompleteModal() {
   const navigate = useNavigate();

SignUp.tsx:

-        <CompleteModal onModalCancel={handleModalCancel} />
+        <CompleteModal />
🤖 Fix all issues with AI agents
In @src/apis/hooks/pots/useGetPots.ts:
- Around line 5-15: The queryKey for useGetPots must include size and ensure
recruitmentRoles is normalized to null for stable caching: inside useGetPots
compute a normalizedRecruitmentRoles (e.g., const normalizedRecruitmentRoles =
recruitmentRoles ?? null or normalize empty/undefined arrays to null) and use
queryKey: ["pots", page, size, normalizedRecruitmentRoles, onlyMine]; keep the
queryFn calling GetPots with the original params (page, size, recruitmentRoles,
onlyMine) but use the normalized value only for the key.

In @src/apis/hooks/users/useGetSignIn.ts:
- Around line 10-19: The switch in mutationFn of useGetSignIn.ts currently falls
back to getNaverLogIn for any unknown or undefined signInType; update it to
validate signInType and handle unknown values explicitly by throwing an Error or
returning a rejected Promise with a clear message (e.g., "Unknown signInType:
<value>") instead of calling getNaverLogIn; keep the existing cases for "google"
-> getGoogleLogIn and "kakao" -> getKakaoLogIn and ensure callers can surface
the error for easier debugging.

In @src/components/commons/Modal/LoginModal/LoginModal.tsx:
- Around line 23-37: Replace the string-template OAuth URL construction for
googleLoginLink, kakaoLoginLink, and naverLoginLink with URLSearchParams-based
builders: build a params object including client_id, redirect_uri,
response_type, scope (where applicable) and a generated state value; append
params.toString() to the provider base auth URL, store the generated state (e.g.
in sessionStorage) for later callback verification, and ensure you URL-encode
redirect_uri/scope by relying on URLSearchParams so callbacks won’t break across
environments and CSRF state checks can be performed on callback handling.

In @src/pages/Callback/Callback.tsx:
- Around line 8-11: loginType from useParams must be validated before using
useGetSignIn/mutate: check that loginType is a non-empty string and one of the
allowed values (e.g., "google" or "naver"); if invalid or undefined, perform a
client redirect to home (or call navigate("/")) and do not call useGetSignIn or
trigger mutate. Ensure this validation occurs early in the Callback component
(before invoking useGetSignIn or using mutate with code) and keep
isCodeProcessed/code handling unchanged.

In @src/pages/Pots/pages/AllPot/AllPotPage.tsx:
- Around line 32-39: The empty if in handleClick causes clicks on "내가 만든 팟" when
not logged in to do nothing; replace it with an early return that triggers the
app's login flow (e.g., call showLoginModal() or openLoginModal() if available,
otherwise show a toast/alert) before returning, and only run setCurrentPage(1),
setSelectedCategory(partName) and setIsMyPot(partName === "내가 만든 팟") in the else
branch; ensure you reference the existing login modal/toast helper (or add one)
so the user is prompted to authenticate rather than seeing no response.
- Around line 25-30: partMap[selectedCategory] can be undefined for cases like
"전체보기", so pass null instead of undefined to the hook; update the useGetPots
call in AllPotPage to ensure recruitmentRoles is either a string or null (e.g.,
replace recruitmentRoles: partMap[selectedCategory] with recruitmentRoles:
partMap[selectedCategory] ?? null) so it matches GetPotsParams (string | null)
and stabilizes server/cache behavior; keep the rest of the parameters
(currentPage, size, onlyMine/isMyPot) unchanged.

In @src/routes/router.tsx:
- Around line 97-99: The callback route currently requires a loginType param
(path: `${routes.callback}/:loginType`) which will fail to match `/callback`
redirects; update the route definition so the loginType is optional (use
`:loginType?` in the path) or register an additional parallel route for
`${routes.callback}` that also renders CallbackPage; modify the entry where path
is defined (the route object referencing routes.callback and element:
<CallbackPage />) so the component loads for both `/callback` and
`/callback/:loginType`, preserving existing useGetSignIn behavior.
🧹 Nitpick comments (5)
src/pages/Pots/PotMain.tsx (1)

21-26: 탭 가드가 “막기만” 해서 UX가 끊기고, 라벨 문자열 의존이라 취약

  • tab.label === "지원한 팟" 대신 tab.path === routes.pot.applied로 가드 조건을 잡는 게 안전합니다.
  • e.preventDefault()만 하면 무반응처럼 보일 수 있어서, 여기서 로그인 모달을 열거나(이미 다른 화면에서 쓰는 패턴처럼) 안내 토스트라도 띄우는 편이 좋아요.
  • 추가로, URL로 직접 /pot/applied 진입하는 케이스는 여기서 못 막으니 페이지/라우트 레벨에서도 인증 가드가 있는지 확인이 필요합니다.
src/apis/types/pot.ts (1)

40-45: recruitmentRoles 이름(복수)과 params 타입(string | null) 의미 불일치 가능

필터가 진짜 “복수 role”을 받는다면 string[]로 올리거나, string을 유지할 거면 CSV/구분자 규칙을 타입/주석으로 고정해 두는 게 안전합니다(백엔드 스펙과 불일치 시 필터가 조용히 깨질 수 있음).

src/pages/Home/Home.tsx (1)

22-24: 모달 기반 로그인 유도 전환은 흐름이 깔끔합니다.

비로그인 시 리다이렉트 대신 LoginModal을 여는 방식이 다른 화면들과도 패턴이 통일돼서 좋습니다. 다만 현재처럼 localStorage.getItem("accessToken") 존재만으로 분기하면 만료 토큰에서 UX가 꼬일 수 있어(“팟 만들러 가기” 이동 후 401 등), 가능하면 중앙 auth 상태/만료 검증 로직으로 점진적 정리만 고려해 주세요.

Also applies to: 27-35, 70-72

src/apis/potAPI.ts (1)

32-44: 파라미터 rename 반영은 OK이며, recruitmentRolesnull 정규화가 더 안전합니다.

지금 변경은 타입/호출부와 일관성이 맞습니다. 런타임에서 recruitmentRolesundefined로 들어올 여지가 있으면(상위 매핑 실패 등) 요청 파라미터가 흔들릴 수 있으니, 가능하면 여기서도 recruitmentRoles: recruitmentRoles ?? null로 한 번 더 가드하는 걸 권장합니다(상위에서 이미 보장하면 생략 가능).

src/components/layouts/SideBar/SideBar.tsx (1)

84-99: private route 접근 제어 로직 확인

비로그인 사용자가 나의팟/채팅 메뉴 클릭 시 로그인 모달을 띄우는 로직이 올바르게 구현되었습니다.

⚡ 성능 최적화 제안 (선택사항)

accessToken 체크를 map 내부에서 반복하는 대신 외부에서 한 번만 수행하도록 개선할 수 있습니다:

+  const accessToken = localStorage.getItem("accessToken");
+  
   return (
     <>
       <div css={mainContainer(top)}>
         <div css={container}>
           {menuItems.map(({ to, icon, activeIcon, label }, index) => {
-            const accessToken = localStorage.getItem("accessToken");
             const isPrivateRoute =
               to === routes.myPot.base || to === routes.chat;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98180ac and 2785f7a.

📒 Files selected for processing (15)
  • src/apis/hooks/pots/useGetPots.ts
  • src/apis/hooks/users/useGetSignIn.ts
  • src/apis/potAPI.ts
  • src/apis/types/pot.ts
  • src/apis/userAPI.ts
  • src/components/cards/CtaCard/CtaCard.tsx
  • src/components/commons/Modal/LoginModal/LoginModal.tsx
  • src/components/layouts/SideBar/SideBar.tsx
  • src/pages/Callback/Callback.tsx
  • src/pages/Home/Home.tsx
  • src/pages/Home/components/Feed/Feed.tsx
  • src/pages/Pots/PotMain.tsx
  • src/pages/Pots/pages/AllPot/AllPotPage.tsx
  • src/pages/SignUp/components/CompleteModal/CompleteModal.tsx
  • src/routes/router.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T19:02:42.735Z
Learnt from: yuseoyeon
Repo: STACKPOT/STACKPOT-FE PR: 265
File: src/apis/types/pot.ts:103-108
Timestamp: 2025-08-18T19:02:42.735Z
Learning: GetPotApplicationResponse 타입의 potRole 필드가 Role에서 PotRole 객체로 변경되었을 때, 실제 사용처는 ApplicantsInformation.tsx와 StartPotModal.tsx에서 applicant.potRole.name 형태로 접근하는 부분들이며, 다른 컴포넌트의 potRole 변수들과 혼동하지 않아야 함

Applied to files:

  • src/apis/types/pot.ts
  • src/apis/potAPI.ts
  • src/pages/Pots/pages/AllPot/AllPotPage.tsx
  • src/apis/hooks/pots/useGetPots.ts
🧬 Code graph analysis (7)
src/apis/userAPI.ts (2)
src/apis/axios/apiUtils.ts (1)
  • apiGet (5-11)
src/apis/types/user.ts (1)
  • LogInResponse (5-9)
src/components/layouts/SideBar/SideBar.tsx (10)
src/components/layouts/Header/Header.style.ts (1)
  • iconStyle (49-54)
src/pages/Home/components/Feed/Feed.style.ts (1)
  • iconStyle (36-40)
src/components/layouts/SideBar/SideBar.style.ts (4)
  • iconStyle (29-32)
  • potIconStyle (34-37)
  • mainContainer (5-14)
  • container (16-27)
src/pages/SignUp/components/Section/Section.style.ts (2)
  • potIconStyle (19-23)
  • container (4-9)
src/pages/SignUp/SignUp.style.ts (2)
  • mainContainer (10-16)
  • container (4-9)
src/pages/SignUp/components/ContractsSection/ContractsSection.style.ts (1)
  • container (4-9)
src/pages/Pots/PotMain.style.ts (1)
  • container (4-9)
src/pages/MyPage/MyPage.style.ts (1)
  • container (4-13)
src/components/commons/TemperatureBar/TemperatureBar.style.ts (1)
  • container (4-10)
src/pages/SignUp/components/CategorySelection/CategorySelection.style.ts (1)
  • container (4-8)
src/apis/potAPI.ts (2)
src/apis/types/pot.ts (2)
  • GetPotsParams (40-45)
  • PotsResponse (47-51)
src/apis/axios/apiUtils.ts (1)
  • authApiGet (21-27)
src/pages/Pots/pages/AllPot/AllPotPage.tsx (1)
src/constants/categories.ts (1)
  • partMap (21-28)
src/components/cards/CtaCard/CtaCard.tsx (1)
src/components/cards/CtaCard/CtaCard.style.ts (4)
  • container (4-19)
  • profileImageStyle (20-25)
  • bodyTextStyle (26-31)
  • buttonContainer (33-35)
src/apis/hooks/users/useGetSignIn.ts (1)
src/apis/userAPI.ts (3)
  • getGoogleLogIn (31-33)
  • getKakaoLogIn (35-37)
  • getNaverLogIn (39-41)
src/apis/hooks/pots/useGetPots.ts (2)
src/apis/types/pot.ts (1)
  • GetPotsParams (40-45)
src/apis/potAPI.ts (1)
  • GetPots (32-44)
🔇 Additional comments (5)
src/pages/Home/components/Feed/Feed.tsx (1)

25-26: 모달 기반 로그인 유도 전환은 일관되고 자연스러움

비로그인 시 navigate 대신 LoginModal을 띄우는 흐름으로 바뀐 점은 UX 측면에서 좋아요. 다만 토큰을 localStorage.getItem(...)로 즉시 조회하는 구조라, “로그인 완료 후” 이 화면이 어떻게 갱신되는지(리다이렉트/리로드로 자연스럽게 재마운트 되는지)만 한 번 확인해 주세요.

Also applies to: 50-51, 66-73, 175-177

src/components/cards/CtaCard/CtaCard.tsx (1)

10-12: 버튼 클릭 시 handleClick이 2번 호출되는 버그 가능(버블링)

현재 카드 컨테이너와 내부 버튼 모두 onClick={handleClick}이라 버튼을 누르면 버블링으로 핸들러가 중복 호출될 수 있습니다(로그인 모달/네비게이션이 2번 트리거). 한쪽만 남기거나 버튼에서 stopPropagation() 처리 권장입니다.

🐛 제안 수정안(버튼에서 버블링 차단)
-        <div css={buttonContainer}>
-          <Button variant="cta" onClick={handleClick}>
+        <div css={buttonContainer}>
+          <Button
+            variant="cta"
+            onClick={(e) => {
+              e.stopPropagation();
+              handleClick();
+            }}
+          >
             {type == "feed" ? "피드 작성" : "팟 만들기"}
           </Button>
         </div>

추가로 <img ... />에는 alt를 넣어주고, type == "feed"type === "feed"로 맞추는 편이 좋아요.

Also applies to: 20-34, 37-54

⛔ Skipped due to learnings
Learnt from: yuseoyeon
Repo: STACKPOT/STACKPOT-FE PR: 265
File: src/components/cards/PotDetailCard/MyPotCard.tsx:109-118
Timestamp: 2025-08-19T10:45:00.586Z
Learning: Button 컴포넌트(src/components/commons/Button/Button.tsx)는 내부적으로 onClick 핸들러에서 e.stopPropagation()을 호출하여 이벤트 버블링을 자동으로 방지함
src/apis/userAPI.ts (2)

31-33: 구글 로그인 API 구현 확인 완료

기존 Kakao 로그인과 동일한 패턴으로 구현되어 일관성이 유지되고 있습니다.


39-41: 네이버 로그인 API 구현 확인 완료

기존 OAuth 로그인 함수들과 동일한 패턴으로 구현되어 코드 일관성이 유지됩니다.

src/components/layouts/SideBar/SideBar.tsx (1)

23-23: 로그인 모달 상태 관리 구현 확인

LoginModal 컴포넌트를 import하고 isLoginModalOpen 상태로 제어하는 구조가 명확합니다.

Also applies to: 54-54

Comment on lines 5 to 15
const useGetPots = ({
page,
size,
recruitmentRole,
recruitmentRoles,
onlyMine,
}: GetPotsParams) => {
return useQuery({
queryKey: ["pots", page, recruitmentRole, onlyMine],
queryFn: () => GetPots({ page, size, recruitmentRole, onlyMine }),
queryKey: ["pots", page, recruitmentRoles, onlyMine],
queryFn: () => GetPots({ page, size, recruitmentRoles, onlyMine }),
select: (data) => data.result,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

queryKeysize를 포함하고 recruitmentRolesnull로 정규화하세요.

현재 queryKey: ["pots", page, recruitmentRoles, onlyMine]size가 달라져도 같은 캐시를 재사용할 수 있어 데이터가 뒤섞일 수 있습니다(특히 다른 페이지/컴포넌트에서 size를 다르게 쓰면 재현). 또한 recruitmentRoles가 런타임에서 undefined가 될 여지가 있으면(상위 매핑 실패) 키 안정성이 떨어집니다.

제안 diff
   return useQuery({
-    queryKey: ["pots", page, recruitmentRoles, onlyMine],
-    queryFn: () => GetPots({ page, size, recruitmentRoles, onlyMine }),
+    queryKey: ["pots", page, size, recruitmentRoles ?? null, onlyMine],
+    queryFn: () => GetPots({ page, size, recruitmentRoles: recruitmentRoles ?? null, onlyMine }),
     select: (data) => data.result,
   });
📝 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 useGetPots = ({
page,
size,
recruitmentRole,
recruitmentRoles,
onlyMine,
}: GetPotsParams) => {
return useQuery({
queryKey: ["pots", page, recruitmentRole, onlyMine],
queryFn: () => GetPots({ page, size, recruitmentRole, onlyMine }),
queryKey: ["pots", page, recruitmentRoles, onlyMine],
queryFn: () => GetPots({ page, size, recruitmentRoles, onlyMine }),
select: (data) => data.result,
});
const useGetPots = ({
page,
size,
recruitmentRoles,
onlyMine,
}: GetPotsParams) => {
return useQuery({
queryKey: ["pots", page, size, recruitmentRoles ?? null, onlyMine],
queryFn: () => GetPots({ page, size, recruitmentRoles: recruitmentRoles ?? null, onlyMine }),
select: (data) => data.result,
});
🤖 Prompt for AI Agents
In @src/apis/hooks/pots/useGetPots.ts around lines 5 - 15, The queryKey for
useGetPots must include size and ensure recruitmentRoles is normalized to null
for stable caching: inside useGetPots compute a normalizedRecruitmentRoles
(e.g., const normalizedRecruitmentRoles = recruitmentRoles ?? null or normalize
empty/undefined arrays to null) and use queryKey: ["pots", page, size,
normalizedRecruitmentRoles, onlyMine]; keep the queryFn calling GetPots with the
original params (page, size, recruitmentRoles, onlyMine) but use the normalized
value only for the key.

Comment on lines +23 to +37
const googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${
import.meta.env.VITE_REST_GOOGLE_API_KEY
}&redirect_uri=${
import.meta.env.VITE_GOOGLE_REDIRECT_URI
}&response_type=code&scope=email`;

const kakaoLoginLink = `https://kauth.kakao.com/oauth/authorize?client_id=${
import.meta.env.VITE_REST_API_KEY
}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code
&scope=account_email
&prompt=login`;
const naverLoginLink = "";

const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${
import.meta.env.VITE_REST_NAVER_API_KEY
}&redirect_uri=${import.meta.env.VITE_NAVER_REDIRECT_URI}`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

OAuth URL은 URLSearchParams로 구성하고 redirect_uri/scope/state를 보장하세요.

현재 문자열 템플릿 방식은 redirect_uri/scope 인코딩 누락으로 로컬/배포 환경별로 깨지기 쉽고, (특히 Naver는) state 누락 시 동작 실패 또는 CSRF 방어 공백이 생길 수 있습니다. URLSearchParams + state(생성 후 sessionStorage 등에 저장, 콜백에서 검증)로 통일하는 편이 안전합니다.

제안 diff (개념 예시)
 const LoginModal: React.FC<LoginModalProps> = ({ onCancel }) => {
-  const googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${
-    import.meta.env.VITE_REST_GOOGLE_API_KEY
-  }&redirect_uri=${
-    import.meta.env.VITE_GOOGLE_REDIRECT_URI
-  }&response_type=code&scope=email`;
+  const oauthState = crypto.randomUUID();
+  sessionStorage.setItem("oauth_state", oauthState);
+
+  const googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({
+    client_id: import.meta.env.VITE_REST_GOOGLE_API_KEY, // (명칭은 client_id인지 확인)
+    redirect_uri: import.meta.env.VITE_GOOGLE_REDIRECT_URI,
+    response_type: "code",
+    scope: "openid email", // 서버 요구 스코프에 맞게 조정
+    state: oauthState,
+  }).toString()}`;
 
   const kakaoLoginLink = `https://kauth.kakao.com/oauth/authorize?client_id=${
     import.meta.env.VITE_REST_API_KEY
   }&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code
 &scope=account_email
 &prompt=login`;
 
-  const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${
-    import.meta.env.VITE_REST_NAVER_API_KEY
-  }&redirect_uri=${import.meta.env.VITE_NAVER_REDIRECT_URI}`;
+  const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?${new URLSearchParams({
+    response_type: "code",
+    client_id: import.meta.env.VITE_REST_NAVER_API_KEY,
+    redirect_uri: import.meta.env.VITE_NAVER_REDIRECT_URI,
+    state: oauthState,
+  }).toString()}`;
📝 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 googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${
import.meta.env.VITE_REST_GOOGLE_API_KEY
}&redirect_uri=${
import.meta.env.VITE_GOOGLE_REDIRECT_URI
}&response_type=code&scope=email`;
const kakaoLoginLink = `https://kauth.kakao.com/oauth/authorize?client_id=${
import.meta.env.VITE_REST_API_KEY
}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code
&scope=account_email
&prompt=login`;
const naverLoginLink = "";
const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${
import.meta.env.VITE_REST_NAVER_API_KEY
}&redirect_uri=${import.meta.env.VITE_NAVER_REDIRECT_URI}`;
const oauthState = crypto.randomUUID();
sessionStorage.setItem("oauth_state", oauthState);
const googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({
client_id: import.meta.env.VITE_REST_GOOGLE_API_KEY,
redirect_uri: import.meta.env.VITE_GOOGLE_REDIRECT_URI,
response_type: "code",
scope: "openid email",
state: oauthState,
}).toString()}`;
const kakaoLoginLink = `https://kauth.kakao.com/oauth/authorize?client_id=${
import.meta.env.VITE_REST_API_KEY
}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code
&scope=account_email
&prompt=login`;
const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?${new URLSearchParams({
response_type: "code",
client_id: import.meta.env.VITE_REST_NAVER_API_KEY,
redirect_uri: import.meta.env.VITE_NAVER_REDIRECT_URI,
state: oauthState,
}).toString()}`;
🤖 Prompt for AI Agents
In @src/components/commons/Modal/LoginModal/LoginModal.tsx around lines 23 - 37,
Replace the string-template OAuth URL construction for googleLoginLink,
kakaoLoginLink, and naverLoginLink with URLSearchParams-based builders: build a
params object including client_id, redirect_uri, response_type, scope (where
applicable) and a generated state value; append params.toString() to the
provider base auth URL, store the generated state (e.g. in sessionStorage) for
later callback verification, and ensure you URL-encode redirect_uri/scope by
relying on URLSearchParams so callbacks won’t break across environments and CSRF
state checks can be performed on callback handling.

Comment on lines +8 to +11
const { loginType } = useParams();
const isCodeProcessed = useRef(false);
const code = new URL(window.location.href).searchParams.get("code");
const { mutate } = useGetSignIn();
const { mutate } = useGetSignIn(loginType);
Copy link

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:

fd "Callback\.tsx" --type f

Repository: STACKPOT/STACKPOT-FE

Length of output: 95


🏁 Script executed:

cat -n src/pages/Callback/Callback.tsx

Repository: STACKPOT/STACKPOT-FE

Length of output: 1019


🏁 Script executed:

fd "useGetSignIn" --type f

Repository: STACKPOT/STACKPOT-FE

Length of output: 100


🏁 Script executed:

cat -n src/apis/hooks/users/useGetSignIn.ts

Repository: STACKPOT/STACKPOT-FE

Length of output: 1344


🏁 Script executed:

rg "Callback" --type tsx --type ts -B 2 -A 2 | head -50

Repository: STACKPOT/STACKPOT-FE

Length of output: 91


🏁 Script executed:

rg "Callback" -B 2 -A 2 | head -50

Repository: STACKPOT/STACKPOT-FE

Length of output: 1746


🏁 Script executed:

cat -n src/routes/router.tsx | grep -A 5 -B 5 "callback"

Repository: STACKPOT/STACKPOT-FE

Length of output: 454


🏁 Script executed:

rg "callback" -i src/constants/routes

Repository: STACKPOT/STACKPOT-FE

Length of output: 124


🏁 Script executed:

fd "routes" -type f | grep -i const

Repository: STACKPOT/STACKPOT-FE

Length of output: 234


🏁 Script executed:

fd "routes" --type file | grep -i const

Repository: STACKPOT/STACKPOT-FE

Length of output: 87


🏁 Script executed:

cat -n src/constants/routes.ts

Repository: STACKPOT/STACKPOT-FE

Length of output: 1066


🏁 Script executed:

rg "useParams" src/pages/Callback/Callback.tsx -A 3 -B 3

Repository: STACKPOT/STACKPOT-FE

Length of output: 506


🏁 Script executed:

rg "navigate.*callback\|href.*callback\|to.*callback" -i

Repository: STACKPOT/STACKPOT-FE

Length of output: 46


🏁 Script executed:

rg "callback" -i | grep -E "href|to=|navigate|link" | head -20

Repository: STACKPOT/STACKPOT-FE

Length of output: 46


🏁 Script executed:

rg "routes\.callback|/callback" -B 3 -A 3

Repository: STACKPOT/STACKPOT-FE

Length of output: 1046


🏁 Script executed:

rg "google|kakao|naver" -i src/pages/Callback/ src/apis/hooks/users/

Repository: STACKPOT/STACKPOT-FE

Length of output: 522


loginType 유효성 검증 필수

URL 파라미터로 받은 loginTypeundefined이거나 유효하지 않은 값일 경우, useGetSignIn 내부의 switch 문에서 기본값으로 네이버 로그인을 실행합니다. 예를 들어, 사용자가 /callback/google이 아닌 /callback으로 이동하거나 잘못된 파라미터를 전달하면 자동으로 네이버 로그인이 시도되어 인증 실패로 이어집니다.

사용자가 구글 로그인을 시도했으나 loginTypeundefined 또는 잘못된 값인 경우, 예상과 다른 네이버 로그인이 실행되어 인증 오류가 발생할 수 있습니다. 이를 방지하기 위해 loginType의 유효성을 검증하고 유효하지 않은 경우 홈으로 리디렉션해야 합니다.

🛡️ loginType 검증 로직 제안
 const Callback: React.FC = () => {
   const { loginType } = useParams();
   const isCodeProcessed = useRef(false);
   const code = new URL(window.location.href).searchParams.get("code");
+  
+  // loginType 유효성 검증
+  useEffect(() => {
+    if (!loginType || !["google", "kakao", "naver"].includes(loginType)) {
+      console.error("Invalid login type:", loginType);
+      navigate(routes.home);
+    }
+  }, [loginType, navigate]);
+  
   const { mutate } = useGetSignIn(loginType);

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

🤖 Prompt for AI Agents
In @src/pages/Callback/Callback.tsx around lines 8 - 11, loginType from
useParams must be validated before using useGetSignIn/mutate: check that
loginType is a non-empty string and one of the allowed values (e.g., "google" or
"naver"); if invalid or undefined, perform a client redirect to home (or call
navigate("/")) and do not call useGetSignIn or trigger mutate. Ensure this
validation occurs early in the Callback component (before invoking useGetSignIn
or using mutate with code) and keep isCodeProcessed/code handling unchanged.

Comment on lines 25 to 30
const { data } = useGetPots({
page: currentPage,
size: 9,
recruitmentRole: partMap[selectedCategory],
recruitmentRoles: partMap[selectedCategory],
onlyMine: isMyPot,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

recruitmentRolesundefined가 들어가지 않게 null 처리하세요.

partMap[selectedCategory]selectedCategory === "전체보기"(기본값) 같은 케이스에서 런타임 undefined가 될 수 있어요. GetPotsParams.recruitmentRolesstring | null인 만큼, “전체보기/내가 만든 팟”은 null로 내려 서버/캐시 동작을 안정화하는 편이 안전합니다.

제안 diff
   const { data } = useGetPots({
     page: currentPage,
     size: 9,
-    recruitmentRoles: partMap[selectedCategory],
+    recruitmentRoles: partMap[selectedCategory] ?? null,
     onlyMine: isMyPot,
   });
🤖 Prompt for AI Agents
In @src/pages/Pots/pages/AllPot/AllPotPage.tsx around lines 25 - 30,
partMap[selectedCategory] can be undefined for cases like "전체보기", so pass null
instead of undefined to the hook; update the useGetPots call in AllPotPage to
ensure recruitmentRoles is either a string or null (e.g., replace
recruitmentRoles: partMap[selectedCategory] with recruitmentRoles:
partMap[selectedCategory] ?? null) so it matches GetPotsParams (string | null)
and stabilizes server/cache behavior; keep the rest of the parameters
(currentPage, size, onlyMine/isMyPot) unchanged.

Comment on lines +97 to 99
path: `${routes.callback}/:loginType`,
element: <CallbackPage />,
},
Copy link

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:

# First, let's check the package.json to confirm react-router-dom version
find . -name "package.json" -type f | head -5

Repository: STACKPOT/STACKPOT-FE

Length of output: 78


🏁 Script executed:

# Let's examine the router.tsx file
find . -name "router.tsx" -type f

Repository: STACKPOT/STACKPOT-FE

Length of output: 87


🏁 Script executed:

# Search for OAuth configuration and callback route setup
rg "callback|oauth|loginType" -i --type tsx --type ts -A 3

Repository: STACKPOT/STACKPOT-FE

Length of output: 91


🌐 Web query:

react-router-dom 7.1.1 optional parameters syntax support :param?

💡 Result:

Yes. React Router v7 (including 7.1.1) supports optional path segments using a trailing ? on the segment — e.g. "/:lang?/categories" or "/blog/:id?" will match with or without that segment. [1][2]

Examples:

  • <Route path="/:lang?/categories" … /> matches /categories and /en/categories. [1]
  • <Route path="/blog/:id?" … /> matches /blog and /blog/10. [1]

Sources: React Router docs on optional segments and the v7 changelog/release (7.1.1). [1][2]


🏁 Script executed:

cat package.json | grep -A 2 -B 2 "react-router"

Repository: STACKPOT/STACKPOT-FE

Length of output: 231


🏁 Script executed:

cat -n ./src/routes/router.tsx | sed -n '80,110p'

Repository: STACKPOT/STACKPOT-FE

Length of output: 1072


🏁 Script executed:

# Search for OAuth callback configurations and redirect URIs
grep -r "redirect_uri\|callback\|loginType" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" . 2>/dev/null | head -20

Repository: STACKPOT/STACKPOT-FE

Length of output: 747


🏁 Script executed:

# Check for routes.callback definition and any related config
grep -r "routes\." --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" . 2>/dev/null | grep -i "callback\|constant" | head -15

Repository: STACKPOT/STACKPOT-FE

Length of output: 219


🏁 Script executed:

# Search for .env files and environment configuration
find . -name ".env*" -type f 2>/dev/null | head -10

Repository: STACKPOT/STACKPOT-FE

Length of output: 46


🏁 Script executed:

# Check for any .env.example or similar
cat .env.example 2>/dev/null || cat .env 2>/dev/null || echo "No .env found in root"

Repository: STACKPOT/STACKPOT-FE

Length of output: 85


🏁 Script executed:

# Search for VITE_REDIRECT_URI references to understand the actual redirect URL pattern
grep -r "VITE_REDIRECT_URI\|VITE_NAVER_REDIRECT_URI" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" . 2>/dev/null

Repository: STACKPOT/STACKPOT-FE

Length of output: 312


🏁 Script executed:

# Check the LoginModal to see exactly how redirect_uri is constructed
cat -n ./src/components/commons/Modal/LoginModal/LoginModal.tsx | grep -A 5 -B 5 "redirect_uri"

Repository: STACKPOT/STACKPOT-FE

Length of output: 1041


🏁 Script executed:

# Check if there's a git history showing if plain /callback route was removed
git log --oneline --all -20 2>/dev/null | head -20

Repository: STACKPOT/STACKPOT-FE

Length of output: 989


🏁 Script executed:

# Check the Callback component to see how it handles loginType
cat -n ./src/pages/Callback/Callback.tsx | head -50

Repository: STACKPOT/STACKPOT-FE

Length of output: 1019


🏁 Script executed:

# Search for useGetSignIn hook to see what loginType values it expects
find . -name "*useGetSignIn*" -o -name "*SignIn*" | grep -i hook

Repository: STACKPOT/STACKPOT-FE

Length of output: 143


🏁 Script executed:

# Look for test files or documentation mentioning callback route
find . -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "callback\|loginType" 2>/dev/null | head -5

Repository: STACKPOT/STACKPOT-FE

Length of output: 46


🏁 Script executed:

# Search for how loginType is determined or what values it can have
grep -r "loginType\|provider" --include="*.ts" --include="*.tsx" . 2>/dev/null | grep -v node_modules | head -20

Repository: STACKPOT/STACKPOT-FE

Length of output: 1780


🏁 Script executed:

# Check the useGetSignIn hook implementation
cat -n ./src/apis/hooks/users/useGetSignIn.ts

Repository: STACKPOT/STACKPOT-FE

Length of output: 1344


🏁 Script executed:

# Search for where loginType values are set or defined (e.g., 'google', 'naver', 'kakao')
grep -r "google\|naver\|kakao" --include="*.ts" --include="*.tsx" . 2>/dev/null | grep -v node_modules | grep -i "type\|provider\|login" | head -15

Repository: STACKPOT/STACKPOT-FE

Length of output: 1622


🏁 Script executed:

# Check git diff or recent changes to understand if this was a recent modification
git log --oneline -p -- ./src/routes/router.tsx 2>/dev/null | head -50

Repository: STACKPOT/STACKPOT-FE

Length of output: 1077


콜백 라우트 호환성 깨질 위험 - :loginType 필수 파라미터 문제

path: \${routes.callback}/:loginType`loginType을 필수 파라미터로 요구합니다. OAuth 제공자(Google, Kakao, Naver)의 redirect_uri가 /callback형태로 설정되어 있거나, 경로에loginType`이 포함되지 않으면 라우트가 매칭되지 않아 NotFound로 떨어집니다.

useGetSignIn이 undefined를 받을 수 있도록 설계되었지만(기본값: naver), 라우트 매칭이 실패하면 컴포넌트 자체가 로드되지 않으므로 이 방어 장치는 작동하지 않습니다.

react-router-dom 7.1.1은 optional 파라미터 문법(?)을 지원하므로, 다음 중 하나로 수정할 것을 권장합니다:

✅ 권장 해결책
- path: `${routes.callback}/:loginType`,
+ path: `${routes.callback}/:loginType?`,

이렇게 하면 /callback/callback/google 모두 매칭됩니다.

또는 하위호환을 위해 두 라우트를 병렬 등록:

+ { path: routes.callback, element: <CallbackPage /> },
  { path: `${routes.callback}/:loginType`, element: <CallbackPage /> },
📝 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
path: `${routes.callback}/:loginType`,
element: <CallbackPage />,
},
path: `${routes.callback}/:loginType?`,
element: <CallbackPage />,
},
🤖 Prompt for AI Agents
In @src/routes/router.tsx around lines 97 - 99, The callback route currently
requires a loginType param (path: `${routes.callback}/:loginType`) which will
fail to match `/callback` redirects; update the route definition so the
loginType is optional (use `:loginType?` in the path) or register an additional
parallel route for `${routes.callback}` that also renders CallbackPage; modify
the entry where path is defined (the route object referencing routes.callback
and element: <CallbackPage />) so the component loads for both `/callback` and
`/callback/:loginType`, preserving existing useGetSignIn behavior.

@yuseoyeon yuseoyeon merged commit e4b5eaf into main Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 네이버, 구글 소셜 로그인 연동

4 participants