Skip to content

#77 feat: 테스트용 이메일 로그인#78

Merged
JiiminHa merged 15 commits intodevelopfrom
feat/77-local-login
Mar 24, 2026
Merged

#77 feat: 테스트용 이메일 로그인#78
JiiminHa merged 15 commits intodevelopfrom
feat/77-local-login

Conversation

@JiiminHa
Copy link
Copy Markdown
Contributor

⚙️ Related ISSUE Number

close #77



📄 Work Description

로컬 로그인 폼 적용 및 학생용 헤더에 채팅 추가



📷 Screenshot

화면 기록 2026-03-24 오후 7 48 59
화면 기록 2026-03-24 오후 7 50 02



💬 To Reviewers



🔗 Reference

@JiiminHa JiiminHa requested a review from suminb99 March 24, 2026 10:51
@JiiminHa JiiminHa self-assigned this Mar 24, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
snow-code-client Ready Ready Preview, Comment Mar 24, 2026 1:26pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

학생 전용 /student/chat 라우트 추가, 테스트용 로컬 이메일 로그인(API·뮤테이션·폼) 도입, 헤더·사용자 역할 동기화·스토어 퍼시스트·STOMP 클라이언트 구성 변경이 포함된 기능 변경입니다. (50단어 이내)

Changes

Cohort / File(s) Summary
App Routing
src/App.tsx
student/chat 중첩 라우트 추가하여 ChatPage 렌더링(학생 전용).
Routes Constants
src/shared/config/routes.ts
ROUTES.STUDENT.CHAT = '/student/chat' 추가; ROUTES.ADMIN.STUDENT 루트/프로필 시그니처 변경.
Local Login API
src/entities/auth/api/authApi.ts
LocalLoginParams 인터페이스 및 localLogin 함수 추가(기존 로그인 엔드포인트에 provider:'LOCAL'로 POST, 기존 응답 스키마 사용).
Local Login Mutations
src/features/auth/local/api/localMutations.ts
localMutations.localLogin React Query mutation 설정 추가.
Local Login UI
src/features/auth/local/ui/LocalLoginForm.tsx
로컬 로그인 폼 컴포넌트 추가(입력 검증, 뮤테이션 호출, 성공 시 store 로그인·네비게이션, 에러 처리).
User ID / Login Page
src/pages/common/UserIdInputPage.tsx
쿼리 파싱을 useSearchParams로 전환, LocalLoginForm 토글·조건부 렌더링 및 일부 UI/스타일 변경.
Header / Auth Sync
src/shared/ui/Header.tsx, src/features/auth/sync-user-role/model/useSyncUserRole.ts
헤더가 useUserStore.userType 기준으로 채팅·로고 링크 분기; useSyncUserRole는 인증 시 역할 동기화 중단 로직 추가.
Chat Page UI
src/pages/chat/ChatPage.tsx
포맷팅 변경 및 레이아웃 클래스 max-w-[1240px]max-w-310로 스타일 수정(기능적 변경 없음).
User Store Persistence
src/entities/auth/model/useUserStore.ts
Zustand persist에 createJSONStorage(() => sessionStorage) 명시적 사용으로 저장소 변경.
STOMP Client
src/shared/lib/stompClient.ts
SockJS → 직접 WebSocket(brokerURL) 전환 및 Authorization 헤더에 raw 토큰 사용으로 변경.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User as 사용자
    participant Form as LocalLoginForm
    participant Query as ReactQuery
    participant API as authApi.localLogin
    participant Store as useUserStore
    participant Router as useNavigate

    User->>Form: 폼 제출(name,email,oAuthToken,studentId?)
    Form->>Form: 입력 검증 (필수/학생 여부)
    Form->>Query: localMutations.localLogin(params) 호출
    Query->>API: POST /auth/login (provider: 'LOCAL', payload)
    API->>API: 응답 검증 (kakaoLoginApiResponseSchema)
    API-->>Query: 성공 응답 (accessToken, memberId, role)
    Query-->>Form: mutation 성공 콜백
    Form->>Store: login(accessToken, memberId, userType)
    Form->>Router: userType에 따라 네비게이션(/admin 또는 /student)
    Router-->>User: 대상 페이지로 이동
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

🧩 feature, 🎨 ui

Suggested reviewers

  • suminb99
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
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.
Linked Issues check ❓ Inconclusive PR은 #77의 테스트용 이메일 로그인 기능 요구사항을 구현했으나, 이슈에서 명시한 경로('/dev/login')와 다르게 '/student/chat' 등 다른 경로를 사용하고 있습니다. 이슈 #77에서 요구한 '/dev/login' 경로 구현 여부를 확인하고, 현재 구현된 로컬 로그인 기능의 접근 경로가 의도된 범위와 일치하는지 검토하세요.
Out of Scope Changes check ❓ Inconclusive PR에는 로컬 로그인 기능 외에도 학생 헤더 채팅 기능, STOMP 클라이언트 변경, 라우팅 구조 개선 등 여러 부가 변경사항이 포함되어 있습니다. 라우팅 구조 변경(ROUTES.ADMIN.STUDENT), STOMP 클라이언트 WebSocket 전환, 헤더 컴포넌트 재구조화 등이 이슈 #77 범위에 포함되는지 또는 기술적 필요성이 있는지 명확히 하세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 테스트용 이메일 로그인 기능 추가라는 변경사항의 주요 내용을 명확하게 반영하고 있습니다.
Description check ✅ Passed PR 설명은 로컬 로그인 폼 적용 및 학생용 헤더 채팅 추가라는 변경사항과 관련이 있으며, 관련 이슈를 명시하고 스크린샷을 제공합니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/77-local-login

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

🧹 Nitpick comments (5)
src/pages/common/UserIdInputPage.tsx (1)

138-144: 로컬 로그인 폼 통합이 잘 되었습니다

테스트용 로컬 로그인 토글이 깔끔하게 구현되었습니다.

한 가지 확인이 필요한 부분: isAdminfalse일 때 studentIduserIdString을 전달하는데, 학번 입력이 완료되지 않은 상태(userIdString.length < 7)에서도 LocalLoginForm이 렌더링될 수 있습니다.

💡 제안: 학번 입력 완료 여부 검증 추가

LocalLoginForm 내부에서 검증하거나, 조건부 렌더링 시 isComplete 체크를 추가하는 것을 고려해보세요:

- {showLocalForm && <LocalLoginForm isAdmin={isAdmin} studentId={isAdmin ? undefined : userIdString} />}
+ {showLocalForm && (
+   <LocalLoginForm 
+     isAdmin={isAdmin} 
+     studentId={isAdmin ? undefined : (isComplete ? userIdString : undefined)} 
+   />
+ )}

또는 LocalLoginForm 컴포넌트에서 studentId 유효성을 검증하여 submit을 막는 방법도 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/common/UserIdInputPage.tsx` around lines 138 - 144, The
LocalLoginForm can be rendered with an incomplete student ID when isAdmin is
false because you pass userIdString unguarded; update the conditional render
around LocalLoginForm (the showLocalForm + LocalLoginForm usage) to only render
when either isAdmin is true or userIdString meets the completed-length check
(e.g., userIdString.length >= 7), or alternatively add a validity prop (e.g.,
isStudentIdComplete) passed into LocalLoginForm and block submission inside
LocalLoginForm; locate the showLocalForm state and the LocalLoginForm component
invocation to implement the chosen guard so the form never receives an
incomplete studentId.
src/features/auth/local/api/localMutations.ts (1)

1-8: 뮤테이션 구조가 적절합니다

React Query 뮤테이션 패턴을 잘 따르고 있습니다. type 키워드를 사용한 type-only import도 가이드라인에 부합합니다.

📝 파일명 컨벤션 제안 (선택사항)

코딩 가이드라인에 따르면 일반 파일명은 kebab-case를 사용해야 합니다:

localMutations.ts → local-mutations.ts

다만 기존 코드베이스의 다른 뮤테이션 파일들이 어떤 컨벤션을 따르는지 확인 후 일관성 있게 적용하시면 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/local/api/localMutations.ts` around lines 1 - 8, The file
naming doesn't follow the kebab-case convention; rename the module file from
localMutations.ts to local-mutations.ts and update all imports referencing
localMutations to the new filename, ensuring exported symbol localMutations and
its members (localLogin, mutationKey, mutationFn) and the type import
LocalLoginParams remain unchanged; run the TypeScript/IDE import fixer or
search-replace across the repo to update usages and verify builds/tests pass.
src/entities/auth/api/authApi.ts (1)

30-42: 엔드포인트 네이밍 개선을 제안합니다

현재 코드는 ENDPOINTS.AUTH.KAKAO_LOGIN 엔드포인트에 provider: 'LOCAL'을 함께 보내는 구조로, 이는 백엔드가 단일 엔드포인트(/oauth2/authorization)에서 여러 인증 제공자(Kakao, Local 등)를 provider 파라미터로 구분하는 설계로 보입니다.

다만, 엔드포인트명 KAKAO_LOGIN은 Local 인증에도 사용되므로 명확하지 않습니다. 다음과 같이 개선하는 것을 권장합니다:

  • ENDPOINTS.AUTH.KAKAO_LOGINENDPOINTS.AUTH.LOGIN 또는 ENDPOINTS.AUTH.AUTH_LOGIN으로 변경
  • 반환 타입 KakaoLoginApiResponseLoginResponse 또는 AuthLoginResponse로 변경 (Kakao 특화 타입이 아니므로)

이렇게 하면 향후 추가 인증 제공자 지원 시 코드 의도가 더욱 명확해질 것입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/auth/api/authApi.ts` around lines 30 - 42, Rename the
Kakao-specific endpoint and response types to generic auth names: update usages
of ENDPOINTS.AUTH.KAKAO_LOGIN to ENDPOINTS.AUTH.LOGIN (or AUTH_LOGIN) and rename
the return type KakaoLoginApiResponse to a generic LoginResponse (and
corresponding kakaoLoginApiResponseSchema to loginApiResponseSchema) so
localLogin and any other auth methods use a non-provider-specific identifier;
update the import/exports where those symbols are defined and adjust localLogin
to return LoginResponse and parse response.data.response with the renamed schema
to keep naming consistent with provider-agnostic behavior.
src/features/auth/local/ui/LocalLoginForm.tsx (2)

37-45: 공백만 입력된 경우 대응 추가를 고려해보세요.

현재 빈 문자열 체크만 하고 있어서, 공백만 입력해도 통과됩니다. 테스트용 폼이라 큰 문제는 아니지만, trim() 추가로 간단히 개선할 수 있습니다.

💡 trim() 적용 예시
  const handleLocalSubmit = () => {
-   if (!localName || !localEmail || !localOAuthToken) {
+   if (!localName.trim() || !localEmail.trim() || !localOAuthToken.trim()) {
      alert('이름, 이메일, OAuthToken을 모두 입력해주세요.');
      return;
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/local/ui/LocalLoginForm.tsx` around lines 37 - 45, The
validation in handleLocalSubmit only checks for empty strings, allowing
whitespace-only input to pass; update the checks to trim inputs before
validation (e.g., use localName.trim(), localEmail.trim(),
localOAuthToken.trim(), and studentId.trim()) so whitespace-only values are
treated as empty and trigger the existing alert paths; adjust the conditional
logic in handleLocalSubmit to use the trimmed values (or check .trim().length
=== 0) and keep the same alerts/flow.

55-103: 시맨틱 HTML: <form> 요소 사용을 권장합니다.

현재 <div>로 감싸져 있어 Enter 키로 폼 제출이 되지 않고, 스크린 리더가 폼으로 인식하지 못합니다. 또한 <label><input>htmlFor/id로 연결되어 있지 않아 접근성이 떨어집니다.

테스트용 로그인 폼이지만, 시맨틱 HTML 사용은 좋은 습관이니까요! 🎯

참고: MDN - Form 접근성

♻️ 시맨틱 폼 구조로 개선하는 예시
  return (
-   <div className='flex flex-col gap-3 w-95 text-left mt-6 animate-in fade-in slide-in-from-top-2 duration-300'>
+   <form
+     onSubmit={(e) => {
+       e.preventDefault();
+       handleLocalSubmit();
+     }}
+     className='flex flex-col gap-3 w-95 text-left mt-6 animate-in fade-in slide-in-from-top-2 duration-300'>
      <div className='flex flex-col gap-2'>
-       <label className='text-xs font-medium text-secondary-black'>
+       <label htmlFor='local-name' className='text-xs font-medium text-secondary-black'>
          테스트용 이름
        </label>
        <input
+         id='local-name'
          type='text'
          placeholder='이름'
          value={localName}
          onChange={(e) => setLocalName(e.target.value)}
          className='border border-stroke rounded-md px-3 py-2.5 text-sm focus:border-primary outline-none transition-colors'
        />
      </div>

      <div className='flex flex-col gap-2'>
-       <label className='text-xs font-medium text-secondary-black'>
+       <label htmlFor='local-email' className='text-xs font-medium text-secondary-black'>
          테스트용 이메일
        </label>
        <input
+         id='local-email'
          type='email'
          placeholder='이메일'
          value={localEmail}
          onChange={(e) => setLocalEmail(e.target.value)}
          className='border border-stroke rounded-md px-3 py-2.5 text-sm focus:border-primary outline-none transition-colors'
        />
      </div>

      <div className='flex flex-col gap-2'>
-       <label className='text-xs font-medium text-secondary-black'>
+       <label htmlFor='local-oauth-token' className='text-xs font-medium text-secondary-black'>
          OAuth Token
        </label>
        <input
+         id='local-oauth-token'
          type='password'
          placeholder='OAuthToken'
          value={localOAuthToken}
          onChange={(e) => setLocalOAuthToken(e.target.value)}
          className='border border-stroke rounded-md px-3 py-2.5 text-sm focus:border-primary outline-none transition-colors'
        />
      </div>

      <button
+       type='submit'
        disabled={isPending}
-       onClick={handleLocalSubmit}
        className='bg-primary text-white text-sm font-semibold py-3.5 rounded-lg cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:bg-primary/90 transition-all mt-2'>
        {isPending ? '로그인 중...' : '로컬 로그인 실행'}
      </button>
-   </div>
+   </form>
  );

Based on learnings: JiiminHa prefers using semantic HTML tags over excessive div usage for better code structure and accessibility in React components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/auth/local/ui/LocalLoginForm.tsx` around lines 55 - 103, The
form wrapper is a plain <div>, and labels aren't linked to inputs, which breaks
keyboard submit and accessibility; update the LocalLoginForm JSX to use a
semantic <form> (replace the outer <div> with a <form>), move the click handler
to an onSubmit that calls handleLocalSubmit and prevents default, set the submit
button to type="submit" (and remove onClick or keep but ensure onSubmit is
primary), and add unique id attributes to the inputs (for localName, localEmail,
localOAuthToken) with corresponding label htmlFor values so labels are
programmatically associated; keep existing state variables and isPending logic
intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/entities/auth/api/authApi.ts`:
- Around line 30-42: Rename the Kakao-specific endpoint and response types to
generic auth names: update usages of ENDPOINTS.AUTH.KAKAO_LOGIN to
ENDPOINTS.AUTH.LOGIN (or AUTH_LOGIN) and rename the return type
KakaoLoginApiResponse to a generic LoginResponse (and corresponding
kakaoLoginApiResponseSchema to loginApiResponseSchema) so localLogin and any
other auth methods use a non-provider-specific identifier; update the
import/exports where those symbols are defined and adjust localLogin to return
LoginResponse and parse response.data.response with the renamed schema to keep
naming consistent with provider-agnostic behavior.

In `@src/features/auth/local/api/localMutations.ts`:
- Around line 1-8: The file naming doesn't follow the kebab-case convention;
rename the module file from localMutations.ts to local-mutations.ts and update
all imports referencing localMutations to the new filename, ensuring exported
symbol localMutations and its members (localLogin, mutationKey, mutationFn) and
the type import LocalLoginParams remain unchanged; run the TypeScript/IDE import
fixer or search-replace across the repo to update usages and verify builds/tests
pass.

In `@src/features/auth/local/ui/LocalLoginForm.tsx`:
- Around line 37-45: The validation in handleLocalSubmit only checks for empty
strings, allowing whitespace-only input to pass; update the checks to trim
inputs before validation (e.g., use localName.trim(), localEmail.trim(),
localOAuthToken.trim(), and studentId.trim()) so whitespace-only values are
treated as empty and trigger the existing alert paths; adjust the conditional
logic in handleLocalSubmit to use the trimmed values (or check .trim().length
=== 0) and keep the same alerts/flow.
- Around line 55-103: The form wrapper is a plain <div>, and labels aren't
linked to inputs, which breaks keyboard submit and accessibility; update the
LocalLoginForm JSX to use a semantic <form> (replace the outer <div> with a
<form>), move the click handler to an onSubmit that calls handleLocalSubmit and
prevents default, set the submit button to type="submit" (and remove onClick or
keep but ensure onSubmit is primary), and add unique id attributes to the inputs
(for localName, localEmail, localOAuthToken) with corresponding label htmlFor
values so labels are programmatically associated; keep existing state variables
and isPending logic intact.

In `@src/pages/common/UserIdInputPage.tsx`:
- Around line 138-144: The LocalLoginForm can be rendered with an incomplete
student ID when isAdmin is false because you pass userIdString unguarded; update
the conditional render around LocalLoginForm (the showLocalForm + LocalLoginForm
usage) to only render when either isAdmin is true or userIdString meets the
completed-length check (e.g., userIdString.length >= 7), or alternatively add a
validity prop (e.g., isStudentIdComplete) passed into LocalLoginForm and block
submission inside LocalLoginForm; locate the showLocalForm state and the
LocalLoginForm component invocation to implement the chosen guard so the form
never receives an incomplete studentId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d94860c-66a4-404f-965a-d88c8b846597

📥 Commits

Reviewing files that changed from the base of the PR and between 0103e0f and 8e15415.

📒 Files selected for processing (9)
  • src/App.tsx
  • src/entities/auth/api/authApi.ts
  • src/features/auth/local/api/localMutations.ts
  • src/features/auth/local/ui/LocalLoginForm.tsx
  • src/features/auth/sync-user-role/model/useSyncUserRole.ts
  • src/pages/chat/ChatPage.tsx
  • src/pages/common/UserIdInputPage.tsx
  • src/shared/config/routes.ts
  • src/shared/ui/Header.tsx

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.

Caution

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

⚠️ Outside diff range comments (1)
src/entities/auth/model/useUserStore.ts (1)

48-55: ⚠️ Potential issue | 🟠 Major

logout() 메서드에서 memberId를 초기화하지 않아 세션에 이전 사용자 식별자가 남습니다

48-54줄의 logout() 함수에서 userType, userName, accessToken, isAuthenticated를 초기화하지만 memberId는 누락되어 있습니다. 60-66줄의 partialize 설정이 memberId를 포함해 sessionStorage에 저장하므로, 로그아웃 후에도 이전 사용자의 memberId가 세션에 남게 됩니다. 개인정보 보호 차원에서 다음과 같이 수정하세요:

수정 제안 (diff)
       logout: () => {
         set({
           userType: 'guest',
           userName: '',
+          memberId: null,
           accessToken: null,
           isAuthenticated: false,
         });
       },

Zustand persist 미들웨어 관련 더 자세한 내용은 공식 문서를 참고하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/auth/model/useUserStore.ts` around lines 48 - 55, The logout()
method currently resets userType, userName, accessToken, and isAuthenticated but
omits memberId, so previous memberId remains in sessionStorage via the persist
partialize; update the logout() implementation (function logout) to also reset
memberId (e.g., to null or empty string) and verify the persist partialize
configuration includes memberId so it is cleared from sessionStorage on logout
(check the partialize closure that lists memberId).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/entities/auth/model/useUserStore.ts`:
- Around line 48-55: The logout() method currently resets userType, userName,
accessToken, and isAuthenticated but omits memberId, so previous memberId
remains in sessionStorage via the persist partialize; update the logout()
implementation (function logout) to also reset memberId (e.g., to null or empty
string) and verify the persist partialize configuration includes memberId so it
is cleared from sessionStorage on logout (check the partialize closure that
lists memberId).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ab41a0b-4c40-4e81-8737-af5b24c7aaa1

📥 Commits

Reviewing files that changed from the base of the PR and between 0cb0493 and 1506d47.

📒 Files selected for processing (3)
  • src/entities/auth/model/useUserStore.ts
  • src/features/auth/local/ui/LocalLoginForm.tsx
  • src/pages/common/UserIdInputPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/common/UserIdInputPage.tsx
  • src/features/auth/local/ui/LocalLoginForm.tsx

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

🧹 Nitpick comments (2)
src/shared/ui/Header.tsx (1)

105-105: 여긴 store 전체보다 필요한 slice만 구독하는 편이 낫습니다.

useUserStore() 전체를 구독하면 accessToken, memberId처럼 이 컴포넌트가 쓰지 않는 값이 바뀔 때도 Header가 함께 리렌더됩니다. Zustand 문서도 selector로 필요한 상태만 읽는 방식을 권장합니다. 지금 파일의 AuthenticatedHeader처럼 필드별 selector로 맞추면 더 가볍습니다. 관련 문서: Zustand selectors. (zustand.docs.pmnd.rs)

♻️ 제안 코드
 export default function Header() {
-  const {userType, isAuthenticated} = useUserStore();
+  const userType = useUserStore((state) => state.userType);
+  const isAuthenticated = useUserStore((state) => state.isAuthenticated);
 
   if (!isAuthenticated) {
     return <GuestHeader />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/Header.tsx` at line 105, Header subscribes to the entire
useUserStore which causes unnecessary re-renders when unrelated fields (like
accessToken/memberId) change; change the subscriptions to selector-based reads
so the component only selects the fields it needs (e.g., replace the combined
destructure in Header with separate selector calls for userType and
isAuthenticated via useUserStore(state => state.userType) and useUserStore(state
=> state.isAuthenticated)), and mirror this selector approach in
AuthenticatedHeader so each component only subscribes to its required slices.
src/shared/lib/stompClient.ts (1)

5-6: brokerURL 조합이 env 값의 path 형태에 묶여 있습니다.

VITE_API_BASE_URL에 trailing slash나 /api 같은 path가 들어오면 현재 구현은 그 path까지 이어붙여 //ws/stomp 또는 /api/ws/stomp를 만들 수 있습니다. WebSocket endpoint를 항상 /ws/stomp로 쓸 의도라면 new URL('/ws/stomp', baseURL)로 경로를 고정한 뒤 protocol만 ws:/wss:로 바꾸는 편이 더 안전합니다. STOMPJS의 brokerURL은 브로커의 WebSocket endpoint 전체 URL을 기대합니다. 관련 문서: STOMPJS Client#brokerURL. (stomp-js.github.io)

🔧 제안 코드
 export const createStompClient = () => {
   const baseURL = import.meta.env.VITE_API_BASE_URL;
-  const brokerURL = baseURL.replace('http', 'ws') + '/ws/stomp';
+  const url = new URL('/ws/stomp', baseURL);
+  url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
+  const brokerURL = url.toString();
 
   return new Client({
     brokerURL: brokerURL,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/lib/stompClient.ts` around lines 5 - 6, brokerURL is built by
string replace on baseURL (import.meta.env.VITE_API_BASE_URL), which can produce
incorrect paths when baseURL contains a trailing slash or path; instead
construct the WebSocket endpoint using the URL API to ensure the path is exactly
'/ws/stomp' and then switch the protocol to 'ws:' or 'wss:' based on the
original URL's protocol; update the code that computes brokerURL (the variable
named brokerURL that currently does baseURL.replace('http','ws') + '/ws/stomp')
to create a new URL('/ws/stomp', baseURL) and set its protocol appropriately
before passing it to STOMPJS Client#brokerURL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/ui/Header.tsx`:
- Around line 111-114: The switch on userType in Header.tsx returns the same
AuthenticatedHeader with showChat={true} for both 'admin' and 'student', which
lets students see admin-only assignment button; update the switch to
differentiate roles—either return different props (e.g., showAssignment or
showChat) per case or split into explicit cases returning <AuthenticatedHeader
showChat={true} showAssignment={userType==='admin'} /> (or a student-specific
header) so that assignmentButton (inside AuthenticatedHeader) is only rendered
for admins and routes using ROUTES.ADMIN.ASSIGNMENTS.MANAGE are not exposed to
students; adjust AuthenticatedHeader prop signature and its assignmentButton
render logic accordingly and ensure navigation uses role-scoped paths (e.g.,
/admin/* vs /student/*) with useNavigate-aware handlers.

---

Nitpick comments:
In `@src/shared/lib/stompClient.ts`:
- Around line 5-6: brokerURL is built by string replace on baseURL
(import.meta.env.VITE_API_BASE_URL), which can produce incorrect paths when
baseURL contains a trailing slash or path; instead construct the WebSocket
endpoint using the URL API to ensure the path is exactly '/ws/stomp' and then
switch the protocol to 'ws:' or 'wss:' based on the original URL's protocol;
update the code that computes brokerURL (the variable named brokerURL that
currently does baseURL.replace('http','ws') + '/ws/stomp') to create a new
URL('/ws/stomp', baseURL) and set its protocol appropriately before passing it
to STOMPJS Client#brokerURL.

In `@src/shared/ui/Header.tsx`:
- Line 105: Header subscribes to the entire useUserStore which causes
unnecessary re-renders when unrelated fields (like accessToken/memberId) change;
change the subscriptions to selector-based reads so the component only selects
the fields it needs (e.g., replace the combined destructure in Header with
separate selector calls for userType and isAuthenticated via useUserStore(state
=> state.userType) and useUserStore(state => state.isAuthenticated)), and mirror
this selector approach in AuthenticatedHeader so each component only subscribes
to its required slices.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0220715c-d463-450b-bf1f-995817e038e8

📥 Commits

Reviewing files that changed from the base of the PR and between 1506d47 and 24ecf49.

📒 Files selected for processing (3)
  • src/App.tsx
  • src/shared/lib/stompClient.ts
  • src/shared/ui/Header.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/App.tsx

Comment thread src/shared/ui/Header.tsx
Copy link
Copy Markdown
Contributor

@suminb99 suminb99 left a comment

Choose a reason for hiding this comment

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

확인했습니다!

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.

feat: 테스트용 이메일 로그인

2 participants