Conversation
- gatherings feature 모듈 생성 (types, api, hooks) - CreateGatheringPage: 모임 생성 폼 및 완료 화면 - InvitePage: 초대 링크 조회 및 가입 신청 - PublicRoute: 비로그인 사용자 전용 라우트 가드 - interceptors: /invite 경로 401 예외 처리 - MainLayout: GNB 아래 여백을 페이지별로 설정하도록 변경
- CurrentUser 타입을 auth.types.ts로 분리 - useAuth에 authQueryKeys 팩토리 적용 - staleTime/gcTime을 Infinity로 변경 (수동 갱신 방식) - useLogout 타입 명시 추가
- isAllowedImageFile 함수 추가 (MIME 타입 기반 검증)
Walkthrough독서모임 생성 기능을 구현하고 인증 타입 구조를 정리했습니다. 모임 생성/참여/초대 API와 React Query 훅, 두 개의 새로운 페이지를 추가하고, 공개 라우트를 통해 비인증 사용자 접근을 제어합니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/features/auth/hooks/useAuth.ts`:
- Around line 17-24: In useAuth's useQuery call (queryFn: fetchCurrentUser,
queryKey: authQueryKeys.me()) you set staleTime: Infinity while also using
refetchOnWindowFocus: true, which never triggers because data is always fresh;
fix by either removing refetchOnWindowFocus if you only need manual refresh, or
change refetchOnWindowFocus to "always" to force refetch on window focus, or set
staleTime to a finite value so refetchOnWindowFocus: true can take effect—apply
the chosen change inside the useQuery options in useAuth.
In `@src/pages/Gatherings/InvitePage.tsx`:
- Around line 42-45: InvitePage passes the original path via state: { from:
location.pathname } but no consumer persists/restores that value, so LoginPage
and the OAuth flow lose it; update LoginPage (where useLocation() is used) to
read useLocation().state?.from and persist it (e.g.,
localStorage.setItem('postLoginRedirect', from)) before initiating the Kakao
OAuth redirect, and update the OAuth callback handler/hook to read that
persisted 'postLoginRedirect' value and call navigate(persistedFrom ||
ROUTES.DEFAULT) after successful login, then clear the stored key; reference
InvitePage, LoginPage, useLocation, and the OAuth callback handler to locate
where to add the read/persist/restore logic.
🧹 Nitpick comments (4)
src/api/interceptors.ts (1)
107-116: 빈 if 블록은 early return으로 개선 가능
isInvitePage조건의 빈 if 블록은 의도 파악이 어렵고 가독성을 해칩니다. early return 패턴으로 흐름을 명확히 하세요.♻️ 개선 제안
if (error.response?.status === 401) { const currentPath = window.location.pathname const isAlreadyOnLogin = currentPath === ROUTES.LOGIN const isInvitePage = currentPath.startsWith(ROUTES.INVITE_BASE) - // 초대 페이지에서는 401을 정상 흐름으로 처리 (비로그인 사용자도 접근 가능) - if (isInvitePage) { - // 캐시 삭제나 리다이렉트 없이 에러만 반환 - } else if (!isAlreadyOnLogin) { + // 초대 페이지 또는 로그인 페이지에서는 리다이렉트하지 않음 + if (isInvitePage || isAlreadyOnLogin) { + return Promise.reject( + new ApiError(code ?? 'UNKNOWN', message ?? error.message, error.response?.status ?? 500), + ) + } + - // React Query 캐시 전체 삭제 (인증 정보 포함) - // 세션이 만료되었으므로 모든 캐시된 데이터는 더 이상 유효하지 않음 - queryClient.clear() - alert('로그인이 필요합니다. 로그인 페이지로 이동합니다.') - window.location.href = ROUTES.LOGIN - } + // React Query 캐시 전체 삭제 (인증 정보 포함) + queryClient.clear() + alert('로그인이 필요합니다. 로그인 페이지로 이동합니다.') + window.location.href = ROUTES.LOGIN }
⚠️ 위 diff 적용 시code,message변수가 401 블록 내에서 선언되어야 하므로, 실제로는 해당 변수 추출 로직(Line 120)을 401 블록 앞으로 이동하거나 early return 시 별도로 에러를 구성해야 합니다.src/routes/PublicRoute.tsx (1)
16-19: 로딩 중에도 공용 화면이 노출됩니다
문제: 인증 확인 중(isLoading)에도 Outlet을 렌더링합니다.
영향: 로그인된 사용자가 잠깐 로그인/초대 화면을 보거나 중복 액션을 할 수 있어요.
대안: 로딩 동안은 로더를 보여주고, 완료 후에만 Outlet을 렌더링하세요.🔧 개선안
export function PublicRoute() { const { data: user, isLoading, isError } = useAuth() - // 로딩 중 또는 에러(401) → 로그인 페이지 표시 - if (isLoading || isError || !user) { - return <Outlet /> - } + if (isLoading) { + return ( + <div className="flex min-h-screen items-center justify-center"> + <p>Loading...</p> + </div> + ) + } + + // 에러(401) 또는 비로그인 → 공개 페이지 표시 + if (isError || !user) { + return <Outlet /> + }As per coding guidelines: React 18 기준의 렌더링 흐름과 hooks 사용이 적절한지 확인해줘.
src/pages/Gatherings/CreateGatheringPage.tsx (2)
56-66: 클립보드 복사 실패 시 사용자 피드백 누락
navigator.clipboard.writeText실패 시 콘솔에만 로그하고 사용자에게는 알림이 없습니다.→ 복사 성공/실패 여부를 사용자가 알 수 없음
→openError를 활용하거나 TODO의 toast로 처리 권장💡 에러 피드백 추가 제안
const handleCopyLink = async () => { const fullUrl = getFullInviteUrl() if (!fullUrl) return try { await navigator.clipboard.writeText(fullUrl) // TODO: toast 알림 표시 } catch (error) { console.error('클립보드 복사 실패:', error) + openError('복사 실패', '클립보드 복사에 실패했습니다. 직접 링크를 복사해주세요.') } }
137-141: 성공 화면에서 trim 전 이름 표시Line 139에서
name을 그대로 표시하지만, API에는name.trim()이 전송됩니다.→ 사용자가 앞뒤 공백을 입력하면 UI와 실제 저장된 값이 다를 수 있음
→createdData.gatheringName사용 권장💡 저장된 값 사용 제안
- <p className="typo-subtitle1 text-primary-300">'{name}'</p> + <p className="typo-subtitle1 text-primary-300">'{createdData?.gatheringName ?? name}'</p>
- useAuth: refetchOnWindowFocus를 'always'로 변경 (staleTime: Infinity와 호환) - interceptors: 빈 if 블록을 명확한 조건문으로 개선 - CreateGatheringPage: 성공 화면에서 서버 응답값 사용
🚀 풀 리퀘스트 제안
closes #38
📋 작업 내용
Figma 디자인 기반으로 독서모임 만들기 및 초대 링크를 통한 가입 기능을 구현했습니다.
독서모임 만들기
초대 링크 가입
🔧 변경 사항
신규 파일
src/features/gatherings/- gatherings feature 모듈 (types, api, hooks)src/pages/Gatherings/CreateGatheringPage.tsx- 모임 생성 페이지src/pages/Gatherings/InvitePage.tsx- 초대 페이지src/routes/PublicRoute.tsx- 비로그인 전용 라우트 가드src/shared/assets/icon/- paper-plane, envelope SVG 추가src/features/auth/auth.types.ts- CurrentUser 타입 분리src/shared/lib/image/- 이미지 파일 검증 유틸리티수정 파일
src/routes/index.tsx- 라우트 추가 및 PublicRoute 적용src/api/interceptors.ts- /invite 경로 401 예외 처리src/shared/layout/MainLayout.tsx- GNB 아래 여백을 페이지별로 설정하도록 변경src/features/auth/- 타입 분리 및 캐싱 전략 개선📸 스크린샷 (선택 사항)
📄 기타
Summary by CodeRabbit
릴리스 노트
신규 기능
버그 수정
리팩토링
✏️ Tip: You can customize this high-level summary in your review settings.