Skip to content

feat: 소셜 로그인 기능 및 회원가입 구현#102

Merged
hyeeuncho merged 13 commits intodevelopfrom
feat/#98/login
Apr 29, 2025
Merged

feat: 소셜 로그인 기능 및 회원가입 구현#102
hyeeuncho merged 13 commits intodevelopfrom
feat/#98/login

Conversation

@hyeeuncho
Copy link
Copy Markdown
Member

@hyeeuncho hyeeuncho commented Apr 27, 2025

소셜 로그인 구현 - 카카오 & 구글

스크린샷 2025-04-27 오후 5 34 52

카카오 로그인

스크린샷 2025-04-27 오후 5 35 16

구글 로그인

스크린샷 2025-04-27 오후 5 35 59

신규 로그인 시 이용약관 동의 페이지로 이동

스크린샷 2025-04-27 오후 5 36 14

이름, 이메일은 자동으로 불러오고 전화번호만 입력받음.

스크린샷 2025-04-27 오후 6 04 13 스크린샷 2025-04-27 오후 6 04 31

전화번호 입력 형식 변경

스크린샷 2025-04-29 오후 2 14 54 스크린샷 2025-04-29 오후 2 15 09

Summary by CodeRabbit

  • 신규 기능

    • 사용자 정보 조회 및 수정 API와 관련 커스텀 훅 추가
    • 로그인 콜백(AuthCallback) 및 로그아웃(LogoutPage) 페이지 추가
    • 사용자 이름 표시 및 로그인 상태 반영 UI 개선
  • 버그 수정

    • 이벤트 상세 페이지에서 이벤트 ID 변경(1 → 21)
  • 리팩터

    • 인증 상태 관리 방식 단순화(토큰 → 로그인 여부, 이름 등)
  • 스타일/UX

    • 로그인 버튼이 로그인 시 사용자 이름으로 표시되도록 개선
    • 전화번호 입력 폼 유효성 검사 규칙을 하이픈 포함 형식으로 변경
  • 기타

    • Storybook 워크플로우의 우분투 버전 업데이트(20.04 → 22.04)
    • API 서버 주소를 환경 변수 기반으로 변경
    • 인증 토큰 자동 갱신 및 만료 시 로그아웃 모달 처리 추가

@hyeeuncho hyeeuncho added the 🔧 Feature 기능 구현 label Apr 27, 2025
@hyeeuncho hyeeuncho self-assigned this Apr 27, 2025
@hyeeuncho hyeeuncho linked an issue Apr 27, 2025 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 27, 2025

"""

Walkthrough

이번 변경사항은 인증 및 사용자 정보 관리 로직의 대대적인 리팩토링과 신규 인증 콜백 플로우 도입, 그리고 사용자 정보 API 및 관련 커스텀 훅 추가에 초점을 맞추고 있습니다. 인증 저장소(authStore)는 토큰 기반에서 로그인 상태 플래그와 사용자 이름 기반으로 단순화되었으며, 인증 콜백을 처리하는 신규 라우트와 컴포넌트가 추가되었습니다. 사용자 정보 조회 및 갱신을 위한 API와 타입, 커스텀 React 훅이 신설되었고, 기존의 일부 페이지들은 이 구조에 맞게 데이터 흐름과 상태 관리를 변경하였습니다. 또한, axios 클라이언트의 API base URL이 환경 변수 기반으로 변경되었습니다.

Changes

파일/경로 변경 요약
src/app/provider/authStore.ts 인증 저장소 리팩토링: access/refresh 토큰 제거, isLoggedIn 및 name 플래그와 login/logout/setName 메서드 도입, 기존 모달 상태 관리 유지
src/app/routes/Router.tsx
src/app/routes/routes.ts
인증 콜백용 신규 라우트 추가(JOIN_ROUTES.authCallback, /authCallback), 로그아웃 라우트 추가, 해당 컴포넌트 연결
src/pages/join/AuthCallback.tsx 인증 콜백 처리 신규 컴포넌트 추가: 쿼리 파라미터(status) 분석, 로그인 처리, 사용자 이름 저장, 조건부 네비게이션 및 에러 처리
src/pages/join/LogoutPage.tsx 로그아웃 처리 신규 컴포넌트 추가: 로그아웃 API 호출 후 루트 경로로 리다이렉션, 실패 시 알림 표시
src/features/join/api/user.ts
src/features/join/model/userInformation.ts
사용자 정보 조회/수정 API 함수(readUser, updateUser) 및 타입(UserInfoRequest/Response) 신설
src/features/join/hooks/useUserHook.ts 사용자 정보 조회/수정용 커스텀 React 훅(useUserInfo, useUserUpdate) 추가, react-query 활용
src/pages/join/InfoInputPage.tsx 사용자 정보 입력 페이지 리팩토링: 하드코딩 중복 체크 배열 제거, 커스텀 훅 기반 데이터 연동, 폼 초기값 및 제출 처리 개선, 로딩 상태 추가
src/pages/home/ui/MainPage.tsx 인증 저장소의 isLoggedIn, name 상태 활용하여 로그인 버튼 라벨 동적 변경(로그인 시 이름 표시), 버튼 클릭 시 모달 열기/닫기 토글
src/pages/event/ui/EventDetailsPage.tsx eventId 하드코딩 값 1 → 21로 변경
src/pages/menu/ui/MyTicketPage.tsx 쿠키 기반 토큰 동기화 로직 및 관련 import 완전 제거
src/shared/types/api/http-client.ts axios base URL을 환경 변수 기반으로 변경, 401 에러 시 토큰 리프레시 시도 및 실패 시 로그아웃 모달 오픈 처리 추가
src/shared/lib/formValidation.ts 전화번호 유효성 검사 규칙 변경: 10~11자리 숫자에서 하이픈 포함 형식(XXX-XXX(X)-XXXX)으로 수정 및 최소 입력 체크 추가
.github/workflows/storybook.yml GitHub Actions runner 버전을 ubuntu-20.04에서 ubuntu-22.04로 업데이트

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AuthCallbackPage
    participant AuthStore
    participant UserAPI
    participant Router

    User->>AuthCallbackPage: 인증 콜백 URL 접근 (status 쿼리 포함)
    AuthCallbackPage->>AuthStore: login() 호출
    AuthCallbackPage->>UserAPI: readUser() 호출
    UserAPI-->>AuthCallbackPage: 사용자 정보 반환
    AuthCallbackPage->>AuthStore: setName(이름) 호출
    AuthCallbackPage-->>Router: (조건부) 페이지 이동
Loading
sequenceDiagram
    participant InfoInputPage
    participant useUserInfo
    participant useUserUpdate
    participant UserAPI

    InfoInputPage->>useUserInfo: 사용자 정보 쿼리
    useUserInfo->>UserAPI: readUser()
    UserAPI-->>useUserInfo: 사용자 정보 반환
    useUserInfo-->>InfoInputPage: 데이터 전달

    InfoInputPage->>useUserUpdate: updateUser(수정 데이터)
    useUserUpdate->>UserAPI: updateUser()
    UserAPI-->>useUserUpdate: 갱신 결과 반환
    useUserUpdate-->>InfoInputPage: 결과/에러 콜백
Loading

Possibly related PRs

  • GoTogether-Inc/frontend#97: authStore의 토큰 관리 로직을 추가하는 PR로, 본 PR에서 해당 토큰 관리 로직을 제거한 점과 대조적으로 authStore 구조 변경에 밀접한 관련이 있음.

Suggested reviewers

  • Yejiin21

Poem

🐇
로그인 토큰은 안녕,
isLoggedIn으로 깔끔하게 변신!
이름도 저장, 콜백도 척척,
API와 훅이 춤을 추네
환경 변수로 주소도 싹—
토끼는 오늘도 코드 밭에서
깡총깡총, 리팩토링을 노래해!

"""


📜 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 8f13ecd and ad8f262.

📒 Files selected for processing (3)
  • src/pages/join/InfoInputPage.tsx (3 hunks)
  • src/pages/join/LogoutPage.tsx (1 hunks)
  • src/shared/types/api/http-client.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/pages/join/InfoInputPage.tsx
  • src/pages/join/LogoutPage.tsx
  • src/shared/types/api/http-client.ts
✨ 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
🪧 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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

🧹 Nitpick comments (3)
src/features/join/model/userInformation.ts (1)

8-13: 사용자 정보 요청 타입 정의

사용자 정보 요청을 위한 인터페이스가 응답 인터페이스와 동일한 구조로 정의되었습니다. 현재는 동일하지만, 향후 요청과 응답 구조가 다를 경우를 대비한 좋은 접근법입니다.

이 두 인터페이스가 현재 동일한 구조인 경우 타입 별칭을 사용하는 것도 고려해볼 수 있습니다:

+type UserInfo = {
+    id: number;
+    name: string;
+    phoneNumber: string;
+    email: string;
+}
+
+export type UserInfoResponse = UserInfo;
+export type UserInfoRequest = UserInfo;
src/features/join/hooks/useUserHook.ts (2)

5-10: React Query 훅 구현이 좋습니다만 몇 가지 개선이 가능합니다.

useUserInfo 훅이 잘 구현되어 있습니다. 그러나 staleTime, cacheTime 또는 재시도 로직과 같은 구성 옵션이 없습니다.

데이터 로딩 상태를 더 효율적으로 관리하기 위해 추가 옵션을 고려해보세요:

export const useUserInfo = () => {
    return useQuery<UserInfoResponse>({
        queryKey: ['userInfo'],
        queryFn: readUser,
+       staleTime: 5 * 60 * 1000, // 5분
+       cacheTime: 10 * 60 * 1000, // 10분
+       retry: 1,
+       refetchOnWindowFocus: false,
    });
};

12-16: useMutation 훅이 성공/오류 콜백을 처리하기 위한 옵션이 부족합니다.

useUserUpdate 훅이 기본 기능은 제공하지만, 소비 컴포넌트가 일관된 방식으로 성공 및 오류 처리를 할 수 있도록 도움이 되는 기능이 없습니다.

useMutation에 기본 콜백을 추가하거나 onSettled 핸들러를 제공하는 것이 좋습니다:

export const useUserUpdate = () => {
    return useMutation<UserInfoResponse, Error, UserInfoRequest>({
        mutationFn: updateUser,
+       onError: (error) => {
+           console.error('사용자 정보 업데이트 오류:', error);
+       },
+       onSettled: () => {
+           // 쿼리 캐시 무효화 또는 업데이트
+           queryClient.invalidateQueries(['userInfo']);
+       }
    });
}

이를 위해서는 상단에 다음을 추가해야 합니다:

import { QueryClient, useMutation, useQuery } from '@tanstack/react-query';

const queryClient = new QueryClient(); // 또는 외부 QueryClient 인스턴스 사용
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bcacd92 and a8184c4.

📒 Files selected for processing (12)
  • src/app/provider/authStore.ts (2 hunks)
  • src/app/routes/Router.tsx (2 hunks)
  • src/app/routes/routes.ts (1 hunks)
  • src/features/join/api/user.ts (1 hunks)
  • src/features/join/hooks/useUserHook.ts (1 hunks)
  • src/features/join/model/userInformation.ts (1 hunks)
  • src/pages/event/ui/EventDetailsPage.tsx (1 hunks)
  • src/pages/home/ui/MainPage.tsx (3 hunks)
  • src/pages/join/AuthCallback.tsx (1 hunks)
  • src/pages/join/InfoInputPage.tsx (5 hunks)
  • src/pages/menu/ui/MyTicketPage.tsx (1 hunks)
  • src/shared/types/api/http-client.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/features/join/api/user.ts (2)
src/features/join/model/userInformation.ts (2)
  • UserInfoResponse (1-6)
  • UserInfoRequest (8-13)
src/shared/types/api/http-client.ts (1)
  • axiosClient (6-13)
src/app/routes/Router.tsx (1)
src/app/routes/routes.ts (1)
  • JOIN_ROUTES (16-21)
src/pages/home/ui/MainPage.tsx (1)
src/app/provider/authStore.ts (1)
  • useAuthStore (17-33)
src/features/join/hooks/useUserHook.ts (2)
src/features/join/model/userInformation.ts (2)
  • UserInfoResponse (1-6)
  • UserInfoRequest (8-13)
src/features/join/api/user.ts (2)
  • readUser (4-7)
  • updateUser (9-12)
🔇 Additional comments (13)
src/shared/types/api/http-client.ts (2)

7-7: 환경 변수를 사용한 API 기본 URL 설정 개선

하드코딩된 URL에서 환경 변수를 사용하는 방식으로 변경한 것은 좋은 접근입니다. 이는 다양한 환경(개발, 스테이징, 프로덕션)에 맞게 쉽게 URL을 구성할 수 있게 해줍니다.


15-22:

Details

❓ Verification inconclusive

인증 토큰 처리 로직 제거에 따른 검증 필요

요청 인터셉터에서 토큰 처리 로직이 제거되었습니다. 이는 authStore 리팩토링과 일치하는 변경이지만, 인증이 필요한 API 요청이 여전히 제대로 작동하는지 확인해야 합니다.

다음 스크립트를 실행하여 인증이 필요한 API 요청 패턴을 확인하세요:

인증이 필요한 엔드포인트에 헤더가 어떻게 설정되고 있는지 확인하고, 인증 흐름이 제대로 작동하는지 테스트해보세요.


🏁 Script executed:

#!/bin/bash
# API 호출 패턴 확인하기
rg -A 3 -B 3 "axiosClient\.(get|post|put|delete)" --type=ts

Length of output: 9296


인증 토큰 처리 로직이 다른 곳으로 이동했는지 확인하기 위해 다음 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# axiosClient 설정 및 인증 토큰 처리 확인

echo "----- src/shared/types/api/http-client.ts (상단) -----"
sed -n '1,200p' src/shared/types/api/http-client.ts

echo "----- authStore 사용 위치 검색 -----"
rg -C3 "authStore" --type ts

echo "----- getAccessToken 또는 비슷한 함수 검색 -----"
rg -C3 "getAccessToken" --type ts

echo "----- axiosClient.defaults.headers 검색 -----"
rg -C3 "defaults.headers" --type ts

echo "----- Authorization 헤더 직접 설정 검색 -----"
rg -i -C3 "authorization" --type ts

Length of output: 5475


쿠키 기반 인증(withCredentials) 전환 후 검증 필요

src/shared/types/api/http-client.ts에서 요청 인터셉터의 Authorization 헤더 주입 로직이 삭제되고, 대신 withCredentials: true를 사용해 쿠키 기반 인증으로 변경된 것으로 보입니다. 이 변경이 백엔드에서 의도한 대로 작동하는지 아래 항목을 확인해주세요.

  • 브라우저 요청에 access_token 쿠키가 포함되어 인증 API 호출이 정상 처리되는지
  • 401 응답 발생 시 js-cookie로 쿠키가 제거되고 useAuthStore.getState().openModal()이 호출되어 로그인 모달이 제대로 뜨는지
  • S3 프리사인드 URL 업로드 시 delete axiosClient.defaults.headers.common.Authorization 로직이 필요하거나 의도대로 동작하는지
src/app/provider/authStore.ts (1)

4-15: 인증 저장소 인터페이스 단순화 개선

토큰 기반 관리에서 로그인 상태 플래그와 사용자 이름 기반으로 단순화한 것은 좋은 접근입니다. 이는 상태 관리를 더 간결하게 만들고 유지보수성을 향상시킵니다.

src/pages/menu/ui/MyTicketPage.tsx (1)

35-35: 토큰 동기화 로직 제거

토큰 동기화 로직이 제거된 것은 authStore 리팩토링과 일치하며, 인증 관리 방식을 단순화하는 좋은 변경입니다. 이제 쿠키에서 토큰을 읽어 저장소에 설정하는 로직 대신 isLoggedIn 플래그를 사용합니다.

다만, 사용자 티켓 정보를 가져오는 API 호출(readTicket.getAll)에 인증이 필요한 경우, 인증 처리가 제대로 이루어지는지 확인이 필요합니다. 현재 인증 헤더 설정 로직이 http-client.ts에서 제거되었기 때문입니다.

src/pages/home/ui/MainPage.tsx (2)

34-34: 인증 상태 관리 개선됨

useAuthStore에서 isLoggedInname을 추가로 구조 분해하여 사용자 로그인 상태와 이름을 활용할 수 있게 되었습니다. 이는 토큰 기반 인증에서 상태 플래그 기반 인증으로의 리팩토링을 반영합니다.


60-60: 사용자 경험 개선

로그인 상태에 따라 버튼 레이블이 동적으로 변경되어 사용자에게 개인화된 경험을 제공합니다. 로그인한 사용자에게는 이름과 함께 "님"을 표시하고, 로그인하지 않은 사용자에게는 "로그인"을 표시합니다.

src/app/routes/routes.ts (1)

20-20: 소셜 로그인 콜백 경로 추가

소셜 로그인 프로세스 완료 후 리다이렉션을 처리하기 위한 인증 콜백 경로가 추가되었습니다. 이 경로는 OAuth 인증 흐름에서 중요한 부분입니다.

src/app/routes/Router.tsx (2)

37-37: AuthCallback 컴포넌트 임포트

소셜 로그인 콜백을 처리하기 위한 새로운 컴포넌트를 임포트했습니다. 이 컴포넌트는 인증 프로세스 완료 후 사용자 정보를 처리할 것으로 예상됩니다.


52-52: 인증 콜백 라우트 구성 추가

소셜 로그인 후 리디렉션을 처리하기 위한 콜백 라우트가 적절하게 구성되었습니다. 인증이 필요하지 않은 페이지로 올바르게 설정되어 있어 인증 흐름이 제대로 작동할 수 있습니다.

src/features/join/model/userInformation.ts (1)

1-6: 사용자 정보 응답 타입 정의

사용자 정보 응답을 위한 인터페이스가 적절하게 정의되었습니다. 기본적인 사용자 식별자와 개인 정보(이름, 전화번호, 이메일)를 포함하고 있습니다.

src/pages/join/AuthCallback.tsx (1)

18-19: 주석 처리된 navigate 코드가 있습니다.

코드에 주석 처리된 navigate 호출이 있는데, 이는 미완성 기능이나 진행 중인 작업을 나타냅니다.

이 주석 처리된 코드의 목적을 확인하고 적절하게 구현하거나 제거해야 합니다. 만약 일부러 비활성화 한 것이라면 이유를 주석으로 명시하는 것이 좋습니다.

if (status === 'new') {
-   //navigate('/join/agreement');
+   // TODO: 합의 페이지로 이동하는 기능 구현 예정
+   // navigate('/join/agreement');
} else {
    setName(data?.name || "사용자");
    console.log(isModalOpen);
-   //navigate('/');
+   // TODO: 홈페이지로 리디렉션 기능 구현 예정
+   // navigate('/');
}

Also applies to: 22-23

src/pages/join/InfoInputPage.tsx (2)

10-11: 로딩 상태 처리가 개선되었습니다.

useUserInfo 훅의 isLoading 상태를 확인하고 로딩 상태를 표시하는 것은 좋은 사용자 경험을 제공합니다.

로딩 상태 처리가 적절하게 구현되어 있습니다. 이는 데이터가 로드되기 전에 사용자에게 피드백을 제공하는 좋은 방법입니다.

Also applies to: 58-60


32-48: updateUser 콜백 처리가 잘 구현되어 있습니다.

mutation 함수를 사용하여 사용자 정보를 업데이트하고 성공 및 오류 콜백을 처리하는 방식이 잘 구현되어 있습니다.

성공 및 오류 상태에 대한 명확한 피드백과 적절한 내비게이션이 구현되어 있습니다. 사용자 경험을 위한 좋은 처리 방식입니다.


const [event, setEvent] = useState<ReadEvent | null>(null);
const eventId = 1; //수정 필요
const eventId = 21; //수정 필요
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

하드코딩된 이벤트 ID에 대한 수정 필요

현재 이벤트 ID가 하드코딩되어 있고 주석에도 '수정 필요'라고 표시되어 있습니다. 실제 프로덕션 환경에서는 URL 파라미터나 상태 관리를 통해 동적으로 eventId를 받아오는 것이 좋습니다.

다음과 같이 URL에서 동적으로 ID를 받아오는 방식을 고려해보세요:

- const eventId = 21; //수정 필요
+ const { eventId } = useParams<{ eventId: string }>();
+ const numericEventId = eventId ? parseInt(eventId, 10) : undefined;

이 경우 Router 설정도 수정해야 합니다. 또한 API 호출 시 eventId가 undefined일 경우의 예외 처리도 필요합니다.

@github-actions
Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
src/shared/types/api/http-client.ts (1)

43-59: 토큰 갱신 로직 구현 완료

토큰 만료 시 자동으로 갱신하는 로직이 잘 구현되었습니다. 그러나 토큰 재시도 메커니즘을 개선하기 위해 몇 가지 제안사항이 있습니다:

  1. 무한 재시도 루프를 방지하기 위한 재시도 플래그 추가
  2. 재시도 횟수 제한 설정

다음과 같이 개선할 수 있습니다:

  async (error: AxiosError<ApiErrorResponse>) => {
    const errorInfo = {
      status: error.response?.status || 'NETWORK_ERROR',
      message: error.response?.data?.message || error.message,
    };

-   const originalRequest = error.config as any;
+   const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean };

    // 401(토큰 만료)일 경우 로그아웃 처리 or 토큰 갱신 가능
    if (errorInfo.status === 401) {
+     // 이미 재시도한 요청인 경우 더 이상 재시도하지 않음
+     if (originalRequest._retry) {
+       Cookies.remove('access_token');
+       useAuthStore.getState().openModal();
+       return Promise.reject(errorInfo);
+     }
+     
+     // 재시도 플래그 설정
+     originalRequest._retry = true;
+     
      try {
        await axios.post(
          `${import.meta.env.VITE_API_BASE_URL}/api/v1/auth/refresh`,
          {},
          {
            withCredentials: true, 
          }
        );
        // 새 토큰이 쿠키에 재설정되었으므로 원래 요청 재시도
        return axiosClient(originalRequest);
      } catch (refreshError) {
        // 리프레시 실패 시 로그아웃 처리
        Cookies.remove('access_token');
        useAuthStore.getState().openModal();
-       return Promise.reject(refreshError);
+       return Promise.reject(errorInfo);
      }
    }
src/pages/join/LogoutPage.tsx (1)

5-22: 로그아웃 페이지 구현 완료, 사용자 경험 개선 필요

로그아웃 기능이 기본적으로 잘 구현되어 있지만, 사용자 경험을 개선할 수 있는 몇 가지 제안사항이 있습니다:

  1. 로딩 상태 표시 추가
  2. 더 친절한 에러 메시지 제공
  3. 로그아웃 성공/실패 시 시각적 피드백 추가

다음과 같이 개선할 수 있습니다:

import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { axiosClient } from '../../shared/types/api/http-client';
+import useAuthStore from '../../../app/provider/authStore';

const LogoutPage = () => {
  const navigate = useNavigate();
+ const { logout } = useAuthStore();
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const logoutProcess = async () => {
+     setIsLoading(true);
      try {
        await axiosClient.post('/oauth/logout');
+       logout(); // 클라이언트 상태 정리
        navigate('/');
      } catch (error) {
        console.error('로그아웃 실패:', error);
-       alert('로그아웃에 실패했습니다. 다시 시도해주세요.');
+       setError('로그아웃에 실패했습니다. 네트워크 연결을 확인하고 다시 시도해주세요.');
+       setIsLoading(false);
      }
    };
-   logout();
+   logoutProcess();
  }, [navigate]);

- return <div>로그아웃 중...</div>;
+ if (isLoading) {
+   return <div className="logout-loading">로그아웃 중...</div>;
+ }
+ 
+ if (error) {
+   return (
+     <div className="logout-error">
+       <p>{error}</p>
+       <button onClick={() => window.location.reload()}>다시 시도</button>
+     </div>
+   );
+ }
+ 
+ return null; // 로딩 중이거나 에러가 없으면 렌더링할 필요 없음 (리다이렉트 됨)
};

export default LogoutPage;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a8184c4 and 8f13ecd.

📒 Files selected for processing (10)
  • .github/workflows/storybook.yml (1 hunks)
  • src/app/provider/authStore.ts (2 hunks)
  • src/app/routes/Router.tsx (3 hunks)
  • src/app/routes/routes.ts (2 hunks)
  • src/pages/home/ui/MainPage.tsx (3 hunks)
  • src/pages/join/AuthCallback.tsx (1 hunks)
  • src/pages/join/InfoInputPage.tsx (2 hunks)
  • src/pages/join/LogoutPage.tsx (1 hunks)
  • src/shared/lib/formValidation.ts (1 hunks)
  • src/shared/types/api/http-client.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/storybook.yml
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/pages/home/ui/MainPage.tsx
  • src/app/routes/Router.tsx
  • src/app/routes/routes.ts
  • src/pages/join/InfoInputPage.tsx
  • src/pages/join/AuthCallback.tsx
  • src/app/provider/authStore.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/pages/join/LogoutPage.tsx (1)
src/shared/types/api/http-client.ts (1)
  • axiosClient (6-13)
src/shared/types/api/http-client.ts (2)
src/shared/types/api/apiResponse.ts (1)
  • ApiErrorResponse (7-9)
src/app/provider/authStore.ts (1)
  • useAuthStore (17-33)
🪛 ESLint
src/shared/types/api/http-client.ts

[error] 39-39: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: storybook
🔇 Additional comments (5)
src/shared/lib/formValidation.ts (1)

10-13:

Details

❓ Verification inconclusive

전화번호 유효성 검사 규칙이 개선되었습니다.

새로운 전화번호 유효성 검사는 하이픈(-)을 포함한 형식으로 강제하여 데이터의 일관성을 높이고 가독성을 개선했습니다. 또한 .min(1) 검사를 추가하여 전화번호가 비어있지 않도록 보장했습니다.

이 패턴은 한국 전화번호 형식(XXX-XXX(X)-XXXX)에 최적화되어 있습니다.

전화번호 입력 시 사용자가 직접 하이픈을 입력해야 하나요? 입력 컴포넌트에서 자동 포맷팅 기능을 제공하는지 확인해주세요. 사용자 경험을 향상시키기 위해 입력 필드에 자동 포맷팅이나 마스크 기능을 고려해보세요.


🏁 Script executed:

#!/bin/bash
# 전화번호 입력 관련 컴포넌트 찾기
rg -A 5 -B 5 "phone|Phone|전화" --type tsx

Length of output: 70


전화번호 입력 컴포넷의 자동 포맷팅 기능 확인 필요

  • src/shared/lib/formValidation.ts에서 하이픈(-) 포함 형식을 강제하고 .min(1)을 추가해 빈 입력을 방지한 유효성 검사 규칙이 잘 반영되었습니다.
  • 사용자 경험을 위해, 실제 입력 컴포넌트가 하이픈을 자동 삽입하거나 마스크 기능을 제공하는지 확인해주세요.
  • 자동 포맷팅 기능이 없다면, react-input-mask 같은 라이브러리 도입 또는 커스텀 마스크 구현을 고려해보시기 바랍니다.
src/shared/types/api/http-client.ts (2)

7-7: 환경 변수를 사용한 API 기본 URL 설정 개선됨

하드코딩된 문자열 대신 환경 변수(import.meta.env.VITE_API_BASE_URL)를 사용하여 baseURL을 설정한 것은 좋은 변경입니다. 이렇게 하면 개발, 테스트, 프로덕션 환경 간에 쉽게 URL을 전환할 수 있습니다.


56-56:

Details

✅ Verification successful

컴포넌트 외부에서 스토어 직접 접근 방식 검토 필요

useAuthStore.getState().openModal()은 컴포넌트 외부에서 스토어에 직접 접근하는 방식으로, React 컴포넌트 패턴과 완전히 일치하지 않습니다. 하지만 인터셉터 내에서는 이 방식이 적절할 수 있습니다.

참고: React 컴포넌트에서는 일반적으로 훅을 직접 사용하는 것이 권장되지만, 컴포넌트 외부의 유틸리티 함수나 인터셉터에서는 getState()를 통한 직접 접근이 필요합니다. Zustand 문서에서 이 패턴의 적절성을 확인해보세요.


🌐 Web query:

Zustand React store access outside component

💡 Result:

Accessing Zustand Store Outside React Components

You can access and manipulate your Zustand store outside React components by leveraging its design, which exposes methods for reading and writing state independently of React's lifecycle.

Direct Access with .getState() and .setState()

Once you've defined your Zustand store, you can access its state and actions in any JavaScript file (e.g., utilities, API handlers, or server code), not just inside React components. This is possible because the store returned by create is more than just a hook — it also exposes helper methods:

import { create } from 'zustand';

const useStore = create((set) => ({
  bears: 0,
  increase: () => set((state) => ({ bears: state.bears + 1 })),
}));

// Outside React components:
const { getState, setState } = useStore;

// Read state
console.log(getState().bears);

// Modify state
setState({ bears: 10 });

// Call actions
getState().increase();

This pattern works well for situations such as logging, integrating with non-React code, or testing[5].

Using Vanilla Stores for Non-React Environments

For advanced use cases—like when you need to share store logic between React and non-React environments—consider using Zustand's "vanilla" store. Import createStore from zustand/vanilla, which creates a store object independent of React:

import { createStore } from 'zustand/vanilla';

const appContextStore = createStore((set) => ({
  clientURL: '',
  setClientURL: (clientURL) => set({ clientURL }),
  firstLoad: true,
  setFirstLoad: (firstLoad) => set({ firstLoad }),
}));

export default appContextStore;

You can access and modify state through appContextStore.getState() and appContextStore.setState(). If you want React components to reactively use this vanilla store, you can connect it using the useStore hook by passing the store instance as an argument[1][3].

Caveats & Best Practices

  • Reactivity: Reading state with getState() outside React will not trigger re-renders in React components. Use the hook inside components for reactive updates.
  • Batched Updates: When updating the state from outside React (especially before React 18), you might need to batch updates to avoid unexpected re-renders. Wrap external actions in unstable_batchedUpdates if you notice issues[4].
  • Consistency: Using the same store instance everywhere ensures consistency. If you combine vanilla stores with React context, ensure you're always referencing the same store object to avoid desynchronization[3].

Summary Table

Access Pattern Usage Context Reactive in Components?
useStore((state) => state.value) Inside React components Yes
useStore.getState() / useStore.setState() Outside React (any JS file) No (state is updated, no re-render)
createStore from zustand/vanilla Non-React or hybrid environments Use with useStore(store, selector)

Zustand's flexibility means you can use its store seamlessly both inside and outside React components, making it a powerful choice for state management needs[5][1][3].

Citations:


getState()를 통한 컴포넌트 외부 접근 패턴은 적절합니다

인터셉터나 비-React 유틸리티 함수 내에서

useAuthStore.getState().openModal();

와 같이 Zustand 스토어에 직접 접근하는 방식은 Zustand 공식 문서에서도 권장하는 패턴입니다.
React 컴포넌트 내부에서는 훅(useAuthStore(selector))을 사용해 반응성을 유지해야 하지만, 컴포넌트 외부 로직에서는 getState()/setState()를 이용해 상태를 조회·조작하는 것이 올바른 접근입니다.

추가 조치가 필요하지 않습니다.

src/pages/join/LogoutPage.tsx (2)

1-4: import 문 구조 적절함

필요한 의존성을 명확하게 가져오고 있으며, 불필요한 import가 없어 깔끔합니다.


11-11:

Details

❓ Verification inconclusive

API 엔드포인트 경로 검증 필요

로그아웃 요청을 /oauth/logout 엔드포인트로 보내고 있습니다. 이 경로가 백엔드 API와 일치하는지 확인해주세요.


🏁 Script executed:

#!/bin/bash
# API 엔드포인트 경로 검증
# axiosClient 설정과 함께 엔드포인트 검색

echo "백엔드 API 엔드포인트 검증 중..."
grep -r "/oauth/logout" --include="*.ts" --include="*.tsx" .
grep -r "logout" --include="*.ts" --include="*.tsx" . | grep -v "LogoutPage.tsx"

Length of output: 715


/oauth/logout API 경로 백엔드 일치 여부 확인 필요

  • src/pages/join/LogoutPage.tsx (11번 줄):
    await axiosClient.post('/oauth/logout');
  • 현재 코드베이스에서 해당 엔드포인트 호출은 이 한 곳뿐이며, 백엔드 API 정의와 정확히 일치하는지 확인 후 필요하면 경로를 수정해주세요.

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@Yejiin21 Yejiin21 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!

@hyeeuncho hyeeuncho merged commit 8ac2b53 into develop Apr 29, 2025
2 checks passed
@hyeeuncho hyeeuncho deleted the feat/#98/login branch April 29, 2025 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧 Feature 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 소셜 로그인 구현 및 회원가입 구현

2 participants