Skip to content

Conversation

@layout-SY
Copy link
Collaborator

@layout-SY layout-SY commented Jun 15, 2025

구현내용

  • 회원 상세 조회 페이지 구현
  • 현재 일부 기능(활동 알림, 참여/생성한 프로젝트, 지원한 프로젝트)의 API를 구상 중으로 데이터 미연결 상태
  • user 페이지의 마이 페이지 형식으로 레이아웃 구현

연관이슈

관련이슈 번호를 close해주세요.
close #340

Summary by CodeRabbit

  • 신규 기능

    • 관리자 페이지에 회원 상세 정보 조회 화면이 추가되었습니다.
    • 관리자 회원 목록에서 각 회원에 대해 '퇴출' 버튼이 표시됩니다.
    • 관리자 회원 상세에서 이메일, 경고 횟수, 가입일 등 추가 프로필 정보가 제공됩니다.
    • 관리자 회원 상세 내 탭 메뉴 및 사이드바가 개선되었습니다.
  • 버그 수정

    • '댓글', '문의글' 등 일부 문구에서 불필요한 소유격이 제거되었습니다.
  • 스타일

    • 관리자 및 일반 사용자 구분에 따라 사이드바와 여러 컴포넌트의 스타일이 개선되었습니다.
    • 프로필 차트 박스가 반응형으로 동작하도록 수정되었습니다.
  • 기타

    • 관리자 경로에서 프로젝트 링크 클릭 시 새 탭으로 열리도록 변경되었습니다.
    • 관리자용 활동 로그 필터 및 경로 구조가 세분화되었습니다.

@layout-SY layout-SY requested a review from YouD0313 June 15, 2025 05:10
@layout-SY layout-SY self-assigned this Jun 15, 2025
@layout-SY layout-SY added the ⛏기능 기능 구현에 대한 라벨 label Jun 15, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 15, 2025

Walkthrough

관리자 페이지에서 회원 상세 정보를 보여주는 새로운 컴포넌트와 스타일 파일, 관련 훅이 추가되었습니다. 회원 상세 조회를 위한 라우트 및 데이터 구조가 확장되었으며, 회원 상세 조회 화면에서 사용자 정보를 불러오고 관리할 수 있도록 구현되었습니다. 기존 회원 목록, 카드, 사이드바 등도 일부 관리 기능에 맞춰 수정되었습니다.

Changes

파일/그룹 변경 요약
src/components/admin/adminUserDetail/AdminUserDetail.styled.ts, src/components/admin/userCard/UserCard.styled.ts, src/pages/admin/adminUser/AdminUser.styled.ts 관리자 회원 상세/카드/목록용 스타일 컴포넌트 추가 및 수정
src/components/admin/adminUserDetail/AdminUserDetail.tsx 회원 상세 정보 조회 및 표시를 위한 신규 컴포넌트 추가
src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx, src/constants/admin/mainItems.ts 회원 목록 관련 라우트 및 타이틀 변경
src/components/admin/userCard/UserCard.tsx, src/pages/admin/adminUser/AdminUser.tsx 회원 카드에 강제 퇴출 버튼 및 콜백 추가, 카드 사용 방식 변경
src/components/common/noContent/NoContent.tsx "내" 접두어 제거로 텍스트 수정
src/components/common/sidebar/Sidebar.styled.ts, src/components/common/sidebar/Sidebar.tsx 관리자 여부에 따라 스타일 및 메뉴 항목 스타일 분기
src/components/user/mypage/ContentTab.tsx, src/components/user/mypage/activityLog/ActivityLog.tsx 관리자 경로 여부에 따라 탭 및 필터 분기 렌더링
src/components/user/mypage/myProfile/profile/Profile.styled.ts, src/components/user/mypage/myProfile/profile/Profile.tsx 프로필 컴포넌트에 관리자용 추가 정보(이메일, 경고, 가입일) 표시, 스타일 개선
src/components/user/userPage/userProjectList/UserProjectList.tsx 관리자 경로 시 프로젝트 링크 새 탭 열기 적용
src/constants/routes.ts 관리자 라우트 세분화 및 신규 경로 추가(스킬/포지션 태그, 댓글 등)
src/constants/user/myPageFilter.ts 관리자용 활동 필터 상수 추가
src/hooks/admin/useGetUserInfo.ts 회원 정보 조회용 커스텀 훅 신규 추가
src/models/auth.ts, src/models/userInfo.ts 회원 정보 인터페이스 확장 및 관리자용 상세 정보 타입 추가
src/routes/AdminRoutes.tsx 관리자 상세/활동/알림 라우트 구조 세분화 및 lazy loading 적용

Sequence Diagram(s)

sequenceDiagram
    participant AdminUserDetail as AdminUserDetail 컴포넌트
    participant useGetUserInfo as useGetUserInfo 훅
    participant API as getUserInfo API
    participant Sidebar as Sidebar
    participant Outlet as Outlet (Nested Route)

    AdminUserDetail->>useGetUserInfo: 사용자 ID로 정보 요청
    useGetUserInfo->>API: getUserInfo(id)
    API-->>useGetUserInfo: 사용자 데이터 반환
    useGetUserInfo-->>AdminUserDetail: userData, isLoading 등 반환
    AdminUserDetail->>Sidebar: 사용자 정보 및 탭 렌더링
    AdminUserDetail->>Outlet: userData context로 하위 라우트 렌더링
Loading

Assessment against linked issues

Objective Addressed Explanation
컨텐츠가 들어가는 공용 모달 생성 (#340) 공용 모달 컴포넌트 생성 및 활용은 확인되지 않음
모달 관리 함수를 전역으로 추가 (#340) 전역 모달 관리 함수 추가 내역 없음
회원 상세 조회 컨텐츠 추가 (#340)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
회원 목록, 카드, 사이드바, 마이페이지 등 다수의 관리자/사용자 관련 UI 및 라우트 구조 전반적 개선 (예: src/components/common/sidebar/Sidebar.styled.ts, src/components/user/mypage/myProfile/profile/Profile.tsx 등) 본 이슈는 "회원 상세 조회 모달" 구현에 한정되나, 여러 파일에서 관리자 UI/라우트/스타일/타입 등 광범위한 개선이 포함되어 있음.
관리자용 활동 필터, 프로필 확장, 프로젝트 링크 새 탭 열기 등 부가적 기능 추가 (src/constants/user/myPageFilter.ts, src/components/user/userPage/userProjectList/UserProjectList.tsx 등) 이슈 범위와 직접적 관련 없는 부가적 관리자 기능(필터, 링크 동작 등)이 포함됨.

Suggested labels

🛠개선

Suggested reviewers

  • YouD0313

Poem

🐇
회원 정보 샅샅이,
관리자는 오늘도 바쁘지!
새로 생긴 상세창,
경고·이메일·가입일도 뙇!
모달은 아직 멀었지만
변화의 바람,
토끼도 두근두근!
🥕

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-20T00_41_41_353Z-debug-0.log


📜 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 306ae0b and 1a018d0.

📒 Files selected for processing (1)
  • src/hooks/admin/useGetUserInfo.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/admin/useGetUserInfo.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test
✨ 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.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @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 explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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

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

🧹 Nitpick comments (23)
src/constants/admin/mainItems.ts (1)

17-20: key 값 및 템플릿 문자열을 간결하게 정리하세요

  1. key: 'allUsers'는 기존 라우트 명세와 달라 가독성이 떨어집니다. users 등으로 통일하면 유지보수성이 좋아집니다.
  2. link: `${ADMIN_ROUTE.users}` 는 템플릿 리터럴이 불필요합니다. 바로 ADMIN_ROUTE.users 로 충분합니다.
-  key: 'allUsers',
-  title: '회원 조회',
-  link: `${ADMIN_ROUTE.users}`,
+  key: 'users',
+  title: '회원 조회',
+  link: ADMIN_ROUTE.users,
src/models/auth.ts (1)

65-69: AllUserInfo 인터페이스가 중복 필드를 선언합니다
AllUser 에 이미 email, warning, createdAt 이 포함돼 있어 불필요한 재-선언입니다. 유지보수 시 타입 정의가 두 군데로 갈라지면 불일치 위험이 높아집니다.

-export interface AllUserInfo extends AllUser {
-  email: string;
-  warning: number;
-  createdAt: string;
-}
+export type AllUserInfo = AllUser; // 필요 시 별칭으로 충분
src/constants/routes.ts (1)

50-56: 유사 라우트 상수가 난립하고 있습니다
appliedProject, applyingProject(복수), joinedProject(단수) 등 패턴이 일관되지 않아 혼동을 줍니다.

  • 네이밍 규칙(동사 시제/복수형)과 실제 path(apply-project vs applied-projects)가 매칭되는지 재점검 필요
  • 중복/오타 여부를 전역 검색으로 검증해 주세요.
src/models/userInfo.ts (1)

27-31: UserInfoAll 의 중복 선언 제거 권고
email, warning, createdAt 은 이미 상위 인터페이스에서 필수입니다. 타입 중복은 유지보수 비용만 늘립니다. type UserInfoAll = UserInfo; 또는 제네릭 활용을 고려하세요.

src/hooks/admin/useGetUserInfo.ts (1)

17-18: 리턴 네이밍 통일성
userData 보다는 API 명세와 동일하게 data 또는 userInfo 등이 일관성 있습니다. 작은 부분이지만 훅들의 사용성을 높여줍니다.

src/components/user/mypage/activityLog/ActivityLog.tsx (2)

9-11: admin 여부 판별은 상수화된 경로·정확한 매칭으로 교체 권장

pathname.includes('/admin') 문자열 검색은
/administer, /myadmin 같은 예상치 못한 경로에서도 true가 나올 수 있어 오검 가능성이 있습니다.
관리자 영역을 식별하는 고유 prefix (예: ROUTES.adminRoot)나 pathname.startsWith() / 정규식으로 정확히 매칭하도록 리팩터링하면 추후 라우트 구조 변경 시 안전합니다.

-const isAdmin = pathname.includes('/admin');
+const isAdmin = pathname.startsWith(ROUTES.adminRoot);

13-16: 삼항 연산자 사용 👍 – 추가로 useMemo로 불필요 렌더 방지 가능

현재 구현은 문제 없으나, filter 배열은 변하지 않는 상수이므로 아래처럼 useMemo로 래핑하면 렌더 시 객체 비교가 불필요해집니다.

-<ContentTab
-  $justifyContent='space-around'
-  filter={isAdmin ? ACTIVITY_FILTER_ADMIN : ACTIVITY_FILTER}
-/>

+const filter = useMemo(
+  () => (isAdmin ? ACTIVITY_FILTER_ADMIN : ACTIVITY_FILTER),
+  [isAdmin],
+);
+
+<ContentTab $justifyContent='space-around' filter={filter} />
src/pages/admin/adminUser/AdminUser.styled.ts (2)

6-7: Spinner 스타일 확장만으로는 시멘틱 차별화 부족

styled(SpinnerContainer)``; 로 클래스만 복사되었는데,
관리자 페이지 전용 크기·정렬이 필요하다면 이곳에서 명시적으로 정의해 두는 편이 유지보수에 도움이 됩니다.
예) margin, height 등을 지정하거나 role="status" 보강.

-export const Spinner = styled(SpinnerContainer)``;
+export const Spinner = styled(SpinnerContainer)`
+  margin-top: 4rem;
+  width: 100%;
+  justify-content: center;
+`;

8-10: 고정 px 보다 rem 사용을 고려

margin-top: 120px;7.5rem 처럼 상대 단위를 사용하면 다양한 DPI 환경에서 일관된 간격을 제공할 수 있습니다.

src/components/common/sidebar/Sidebar.styled.ts (1)

52-68: Admin/사용자 메뉴 배경색 분기 – 테마 변수로 묶어 중복 제거

삼항 중첩으로 가독성이 다소 떨어집니다.
const bgActive = $isAdmin ? theme.color.white : theme.color.lightgrey; 같이 변수로 분리하거나, theme 에 sidebar.bg.active.admin / user 를 정의하면 스타일 일관성과 유지보수가 쉬워집니다.

src/components/user/mypage/myProfile/profile/Profile.tsx (4)

18-22: 타입 정의를 컴포넌트 외부로 분리

useOutletContext 의 제네릭 타입을 컴포넌트 내부에서 직접 객체 리터럴로 작성하면 재사용성이 떨어집니다. 별도 타입 별칭을 선언해 두면 다른 하위 컴포넌트에서도 동일 타입을 쉽게 import 할 수 있습니다.


24-25: 불필요한 삼항 연산자 제거

includes(...) 는 이미 boolean 을 반환합니다. 아래처럼 간결하게 표현할 수 있습니다.

-const myPage = location.pathname.includes('mypage') ? true : false;
-const admin = location.pathname.includes('admin') ? true : false;
+const myPage = location.pathname.includes('mypage');
+const admin = location.pathname.includes('admin');
🧰 Tools
🪛 Biome (1.9.4)

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 25-25: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


39-44: 옵셔널 체이닝으로 가독성 향상

두 단계의 if 문 대신 옵셔널 체이닝을 사용하면 의도가 더 명확합니다.

-useEffect(() => {
-  if (scrollRef) {
-    if (scrollRef.current) {
-      scrollRef.current.scrollTop = 0;
-    }
-  }
-}, [scrollRef]);
+useEffect(() => {
+  scrollRef?.current && (scrollRef.current.scrollTop = 0);
+}, [scrollRef]);

60-83: 관리자 전용 정보 렌더링 중 코드 중복

<MyProfileWrapper> 블록이 세 번 반복됩니다. label 과 value 만 바뀌므로 컴포넌트로 추출하거나 map 순회로 줄이면 유지보수가 쉬워집니다.

src/components/admin/adminUserDetail/AdminUserDetail.styled.ts (3)

1-4: 파일 주석 경로 오타

주석 경로가 userDetail 로 표기돼 있습니다. 실제 디렉터리(adminUserDetail) 와 맞지 않아 추후 검색 시 혼란을 줄 수 있으니 수정 권장합니다.


38-44: MainContent 높이 단위 재검토

height: 80% 는 부모 요소의 높이에 강하게 의존합니다. 부모가 auto 일 경우 의도한 레이아웃이 깨질 수 있으므로 min-height 또는 flex-box 기반 레이아웃으로 대체하는 편이 안전합니다.


51-58: DetailContent 의 80vh 는 이중 스크롤 유발 가능성

상위 컨테이너도 스크롤이 있을 때, 내부 80vh 고정은 UX 상 불필요한 이중 스크롤을 만들 수 있습니다. 필요하다면 max-height 를 사용하거나 상위 컨테이너에만 스크롤을 주는 구조를 고려해 주세요.

src/components/admin/userCard/UserCard.tsx (1)

20-21: 불필요한 주석 제거

{/* {userData.userState !== '정지' && */ 주석은 의미를 알기 어렵고 코드 가독성을 해칩니다. 기능 완성 전이라도 불필요한 주석은 삭제해 주세요.

src/pages/admin/adminUser/AdminUser.tsx (2)

12-12: Spinner 컴포넌트 위치 일관성 확인

유저 목록 페이지에서도 공통 스피너 컴포넌트를 사용하셨는데, 다른 Admin 페이지는 LoadingSpinner 를 사용하고 있습니다. 컴포넌트를 통일하거나 import 경로를 통합해 일관성을 유지해 주세요.


19-25: 로딩 상태 Spinner 컨테이너 재사용 권장

<S.Spinner> 래퍼가 여기서만 쓰인다면 styled-component 를 분리할 필요 없이 Spinner 자체에 margin 을 주거나 공통 래퍼를 만드는 편이 코드를 줄일 수 있습니다.

src/components/admin/adminUserDetail/AdminUserDetail.tsx (3)

30-66: TabKey 불일치: 'inquiry' 타입만 선언되고 실제 탭이 없음

TabKey 유니온 타입에 'inquiry'가 정의돼 있지만 tabs 배열엔 존재하지 않습니다. 타입 신뢰성이 떨어지니 둘 중 하나를 맞춰주세요.


30-66: 불필요한 매 렌더 배열 생성

tabs 배열은 props 에 의존하지 않아도 되므로 useMemo로 래핑해 재생성 비용을 줄일 수 있습니다.

-  const tabs: { ... }[] = [
+  const tabs = React.useMemo(
+    () => [
       // ...
-  ];
+    ],
+    [userId]
+  );

70-97: 접근성: “목록으로 이동” 링크에 aria-label 추가 권장

시각장애인 사용자가 목적을 명확히 파악할 수 있도록 aria-label을 부여하는 것이 좋습니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ebef691 and 0c2256f.

📒 Files selected for processing (21)
  • src/components/admin/adminUserDetail/AdminUserDetail.styled.ts (1 hunks)
  • src/components/admin/adminUserDetail/AdminUserDetail.tsx (1 hunks)
  • src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx (1 hunks)
  • src/components/admin/userCard/UserCard.styled.ts (1 hunks)
  • src/components/admin/userCard/UserCard.tsx (1 hunks)
  • src/components/common/noContent/NoContent.tsx (1 hunks)
  • src/components/common/sidebar/Sidebar.styled.ts (1 hunks)
  • src/components/common/sidebar/Sidebar.tsx (2 hunks)
  • src/components/user/mypage/ContentTab.tsx (2 hunks)
  • src/components/user/mypage/activityLog/ActivityLog.tsx (1 hunks)
  • src/components/user/mypage/myProfile/profile/Profile.styled.ts (1 hunks)
  • src/components/user/mypage/myProfile/profile/Profile.tsx (3 hunks)
  • src/constants/admin/mainItems.ts (1 hunks)
  • src/constants/routes.ts (1 hunks)
  • src/constants/user/myPageFilter.ts (1 hunks)
  • src/hooks/admin/useGetUserInfo.ts (1 hunks)
  • src/models/auth.ts (1 hunks)
  • src/models/userInfo.ts (1 hunks)
  • src/pages/admin/adminUser/AdminUser.styled.ts (1 hunks)
  • src/pages/admin/adminUser/AdminUser.tsx (3 hunks)
  • src/routes/AdminRoutes.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (12)
src/constants/admin/mainItems.ts (1)
src/constants/routes.ts (1)
  • ADMIN_ROUTE (34-56)
src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx (1)
src/constants/routes.ts (1)
  • ADMIN_ROUTE (34-56)
src/components/user/mypage/ContentTab.tsx (1)
src/components/user/customerService/MoveInquiredLink.tsx (1)
  • MovedInquiredLink (5-13)
src/constants/user/myPageFilter.ts (1)
src/constants/routes.ts (1)
  • ROUTES (1-32)
src/routes/AdminRoutes.tsx (2)
src/constants/routes.ts (1)
  • ADMIN_ROUTE (34-56)
src/components/user/mypage/notifications/Notifications.tsx (1)
  • Notifications (4-8)
src/hooks/admin/useGetUserInfo.ts (3)
src/models/userInfo.ts (1)
  • ApiUserInfo (33-35)
src/hooks/queries/keys.ts (1)
  • userInfoKey (16-20)
src/api/userpage.api.ts (1)
  • getUserInfo (5-14)
src/components/user/mypage/activityLog/ActivityLog.tsx (2)
src/components/user/mypage/ContentTab.tsx (1)
  • ContentTab (20-80)
src/constants/user/myPageFilter.ts (2)
  • ACTIVITY_FILTER_ADMIN (30-33)
  • ACTIVITY_FILTER (25-28)
src/models/userInfo.ts (1)
src/models/tags.ts (2)
  • PositionTag (10-14)
  • SkillTag (3-8)
src/components/admin/adminUserDetail/AdminUserDetail.styled.ts (1)
src/components/common/sidebar/Sidebar.styled.ts (1)
  • Container (3-13)
src/components/admin/userCard/UserCard.styled.ts (3)
src/components/admin/mainCard/MainCard.styled.ts (1)
  • Wrapper (39-42)
src/components/admin/previewComponent/inquiresPreview/InquiresPreview.styled.ts (1)
  • Wrapper (11-16)
src/components/admin/previewComponent/reportsPreview/ReportsPreview.styled.ts (1)
  • Wrapper (11-16)
src/components/user/mypage/myProfile/profile/Profile.tsx (2)
src/models/userInfo.ts (1)
  • UserInfoAll (27-31)
src/util/formatDate.ts (1)
  • formatDate (3-6)
src/components/admin/userCard/UserCard.tsx (1)
src/models/auth.ts (1)
  • AllUser (60-63)
🪛 Biome (1.9.4)
src/components/user/mypage/myProfile/profile/Profile.tsx

[error] 24-24: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)


[error] 25-25: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test
🔇 Additional comments (9)
src/models/auth.ts (1)

71-74: totalPagestotalPage 변경이 사용처에 미치는 영향 확인 필요
프로퍼티명을 단·복수형으로 바꾸면서 컴파일 오류가 발생할 가능성이 큽니다. 기존 컴포넌트/훅에서 totalPages 를 참조하는지 전역 검색으로 확인해 주세요.

src/models/userInfo.ts (1)

19-20: warning 필드를 필수값으로 바꾼 영향 점검
기존 코드에서 warning 을 선택적으로 다루던 곳이 있다면 컴파일 에러가 발생합니다. 전역적으로 warning? 사용처를 확인하고 기본값 혹은 null-safe 처리를 추가해 주세요.

src/components/common/noContent/NoContent.tsx (1)

20-22: 관리자 뷰와 문구 일관성 확보 확인

'내' 접두사를 제거한 변경이 다른 관리자용 필터(예: ACTIVITY_FILTER_ADMIN)와 표현을 통일시키는 목적임을 확인했습니다. 추가적인 문제는 보이지 않습니다.

src/components/user/mypage/ContentTab.tsx (1)

63-64: 조건부 렌더링 OK – 유틸 분리 시 함께 교체 필요

!isAdmin && <MovedInquiredLink /> 조건은 의도대로 동작합니다. 위에서 제안한 공용 훅으로 isAdmin 판별을 변경할 경우 이 부분도 같이 수정해 주세요.

src/components/common/sidebar/Sidebar.tsx (1)

61-62: 스타일드 컴포넌트 prop 타입 확인 필요

$isAdmin={isAdmin}를 전달했으므로 MenuItem 스타일 정의에 ($isAdmin?: boolean) 타입이 선언되어 있는지 다시 한번 확인해 주세요. 없으면 TS 오류가 발생할 수 있습니다.

src/constants/user/myPageFilter.ts (1)

30-33: 관리자 전용 필터 추가 확인

ACTIVITY_FILTER_ADMIN 상수가 잘 추가되었습니다. 동일한 URL/ID를 갖는 두 상수 간의 분기 로직이 제대로 적용됐는지만 테스트로 확인해 주세요.

src/components/user/mypage/myProfile/profile/Profile.styled.ts (1)

135-139: aspect-ratio 브라우저 지원 확인 필요

aspect-ratio: 1 / 1; 는 최신 브라우저에서는 지원되지만, 일부 구형 Safari/Android WebView에서는 무시됩니다. 대응이 필요하다면 padding-top:100% 트릭을 fallback 으로 추가하거나, 호환성 문서에 명시해 주세요.

src/routes/AdminRoutes.tsx (2)

156-171: 중첩 라우트의 index 리다이렉트는 👍

/log 하위에서 comments 로 리다이렉트하도록 설정한 부분이 명확합니다. 추가 의견 없습니다.


64-72:

Details

✅ Verification successful

불필요하게 깊은 상대 경로, 실제 파일 위치 확인 필요

../components/user/mypage/activityLog/commentsActivity/CommentsActivity../components/user/mypage/activityLog/inquiries/Inquiries 경로가 실제 디렉터리 구조와 불일치할 가능성이 있습니다. 폴더 이름이 조금이라도 달리 작성되면 런타임에서 Lazy-loading 실패가 발생합니다. tsconfigpaths 별칭을 사용하거나, import 경로를 한 번 더 검증해 주세요.


다음으로 실제 파일 위치를 확인해 보겠습니다. 아래 스크립트를 실행하여 CommentsActivityInquiries 컴포넌트 파일이 프로젝트 내 어디에 있는지 검색합니다.


🏁 Script executed:

#!/bin/bash
# 컴포넌트 파일 위치 검색
echo "🔍 CommentsActivity 파일 위치 검색:"
fd -e tsx CommentsActivity
echo "🔍 Inquiries 파일 위치 검색:"
fd -e tsx Inquiries

Length of output: 364


경로 유효성 확인 완료: 불일치 없음

src/components/user/mypage/activityLog/commentsActivity/CommentsActivity.tsx
src/components/user/mypage/activityLog/inquiries/Inquiries.tsx 파일이 실제 존재하며, 현재 lazy import 경로와 일치합니다.
따라서 런타임 에러는 발생하지 않으며, 수정이 필요하지 않습니다.

참고로, 가독성 향상을 위해 tsconfig.jsonpaths 별칭 설정을 적용하는 것은 선택 사항입니다.

Comment on lines 7 to 15
const useGetUserInfo = (id: number) => {
const isLoggedIn = useAuthStore.getState().isLoggedIn;

const { data, isLoading, isFetching } = useQuery<ApiUserInfo>({
queryKey: [userInfoKey.userProfile, id],
queryFn: () => getUserInfo(id),
staleTime: 1 * 60 * 1000,
enabled: isLoggedIn,
});
Copy link

@coderabbitai coderabbitai bot Jun 15, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스토어 구독 누락으로 로그인 상태 변화가 반영되지 않습니다
useAuthStore.getState() 로 스냅샷만 가져오면 로그아웃/로그인 시 enabled 가 즉시 재평가되지 않습니다. 훅 안에서 구독형 사용을 권장합니다.

-const isLoggedIn = useAuthStore.getState().isLoggedIn;
+const isLoggedIn = useAuthStore(state => state.isLoggedIn);

또한 queryKey 가 중첩 배열([['userProfile'], id]) 형태가 됩니다. spread 로 평탄화하면 예측 가능성이 높습니다.

-queryKey: [userInfoKey.userProfile, id],
+queryKey: [...userInfoKey.userProfile, id],
📝 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 useGetUserInfo = (id: number) => {
const isLoggedIn = useAuthStore.getState().isLoggedIn;
const { data, isLoading, isFetching } = useQuery<ApiUserInfo>({
queryKey: [userInfoKey.userProfile, id],
queryFn: () => getUserInfo(id),
staleTime: 1 * 60 * 1000,
enabled: isLoggedIn,
});
const useGetUserInfo = (id: number) => {
// Subscribe to auth store so isLoggedIn updates trigger a re-evaluation
const isLoggedIn = useAuthStore(state => state.isLoggedIn);
const { data, isLoading, isFetching } = useQuery<ApiUserInfo>({
// Flatten the key array for predictability
queryKey: [...userInfoKey.userProfile, id],
queryFn: () => getUserInfo(id),
staleTime: 1 * 60 * 1000,
enabled: isLoggedIn,
});
🤖 Prompt for AI Agents
In src/hooks/admin/useGetUserInfo.ts around lines 7 to 15, replace the direct
state snapshot call useAuthStore.getState() with a subscription hook like
useAuthStore(state => state.isLoggedIn) to ensure the hook re-renders on login
state changes. Also, flatten the queryKey array by spreading
userInfoKey.userProfile and id into a single-level array to avoid nested arrays
and improve predictability.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

getState() 함수 사용과 state의 차이가 뭐야?

Copy link

Choose a reason for hiding this comment

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

@layout-SY 좋은 질문이네요! 두 방식의 핵심적인 차이점을 설명드릴게요.

getState() vs useAuthStore(selector)의 차이

1. getState() - 스냅샷 방식

const isLoggedIn = useAuthStore.getState().isLoggedIn;
  • 한 번만 실행: 컴포넌트가 처음 렌더링될 때 그 순간의 상태값만 가져옴
  • 구독 없음: 이후 isLoggedIn 상태가 변해도 컴포넌트는 모름
  • 정적 값: 변수에 저장된 값은 계속 초기값 그대로 유지

2. Selector 방식 - 구독

const isLoggedIn = useAuthStore(state => state.isLoggedIn);
  • 구독 생성: 스토어의 isLoggedIn 값을 계속 감시
  • 자동 업데이트: 값이 변할 때마다 컴포넌트가 자동으로 리렌더링
  • 반응형: 로그인/로그아웃 시 즉시 반영

실제 동작 예시

// 시나리오: 사용자가 로그아웃한 경우

// ❌ getState() 방식
const useGetUserInfo = (id) => {
  const isLoggedIn = useAuthStore.getState().isLoggedIn; // 처음에 true였다면 계속 true
  
  return useQuery({
    enabled: isLoggedIn, // 로그아웃해도 계속 true -> 쿼리가 계속 실행됨 ⚠️
    // ...
  });
};

// ✅ selector 방식  
const useGetUserInfo = (id) => {
  const isLoggedIn = useAuthStore(state => state.isLoggedIn); // 로그아웃시 즉시 false로 변경
  
  return useQuery({
    enabled: isLoggedIn, // 로그아웃하면 즉시 false -> 쿼리 중단 ✅
    // ...
  });
};

요약: getState()는 "지금 이 순간 값이 뭐야?"이고, selector는 "값이 바뀔 때마다 알려줘"입니다. React 컴포넌트에서는 상태 변화에 반응해야 하므로 selector 방식이 적합해요! 🎯

Copy link
Collaborator

Choose a reason for hiding this comment

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

컴포넌트에서 불러올때 재렌더되면 다시 불러오는거 아냐?

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +23 to 24
const isAdmin = pathname.includes('/admin');

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isAdmin 판별 로직 개선 제안

pathname.includes('/admin')/administrator 같이 의도치 않은 경로까지 관리자 페이지로 오인할 가능성이 있습니다. 정규식이나 startsWith를 사용해 세그먼트 단위로 확인하거나 공용 유틸/훅(useIsAdminPath())으로 분리해 중복을 제거하는 편이 안전합니다.

-const isAdmin = pathname.includes('/admin');
+const isAdmin = /^\/admin(\/|$)/.test(pathname); // 혹은 공통 훅으로 추출
📝 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 isAdmin = pathname.includes('/admin');
// 기존: const isAdmin = pathname.includes('/admin');
const isAdmin = /^\/admin(\/|$)/.test(pathname); // 혹은 공통 훅(useIsAdminPath)으로 추출
🤖 Prompt for AI Agents
In src/components/user/mypage/ContentTab.tsx around lines 23 to 24, the current
check using pathname.includes('/admin') can mistakenly identify paths like
'/administrator' as admin. Replace this with a more precise check using a
regular expression or pathname.startsWith('/admin') to ensure only exact admin
paths match. Alternatively, extract this logic into a shared utility or custom
hook like useIsAdminPath() to centralize and reuse the admin path detection
logic safely.

Comment on lines +27 to 28
const isAdmin = location.pathname.includes('/admin');

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isAdmin 중복 로직 공통화 권장

여러 컴포넌트에서 동일한 pathname.includes('/admin') 체크가 반복되고 있습니다. 전역 util 함수나 커스텀 훅으로 추출해 중복을 제거하면 유지보수성이 높아집니다.

🤖 Prompt for AI Agents
In src/components/common/sidebar/Sidebar.tsx around lines 27 to 28, the check
for isAdmin using location.pathname.includes('/admin') is duplicated across
multiple components. Refactor by extracting this logic into a global utility
function or a custom hook that returns the isAdmin boolean. Replace the inline
check with a call to this shared function or hook to improve maintainability and
reduce code duplication.

Comment on lines 13 to 33
export const Wrapper = styled.div`
position: relative;
display: flex;
justify-content: center;
align-items: center;
`;

export const BanArea = styled.div`
position: absolute;
top: 0px;
right: 0px;
`;

export const BanButton = styled.button`
width: 40px;
border: 1px solid ${({ theme }) => theme.color.lightgrey};
border-radius: ${({ theme }) => theme.borderRadius.primary};
background-color: ${({ theme }) => theme.color.red};
color: ${({ theme }) => theme.color.white};
`;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ban 버튼 접근성·사용성 강화 필요

  1. cursor: pointer; 가 없어 버튼임을 시각적으로 전달하지 못합니다.
  2. 높이 미지정으로 0 height 가 렌더될 수 있습니다(내용 없는 icon 상황).
  3. :hover, :focus-visible 상태 색상 정의가 없어 키보드 내비게이션 시 피드백 부족합니다.
 export const BanButton = styled.button`
+  height: 40px;
   width: 40px;
   border: 1px solid ${({ theme }) => theme.color.lightgrey};
   border-radius: ${({ theme }) => theme.borderRadius.primary};
   background-color: ${({ theme }) => theme.color.red};
   color: ${({ theme }) => theme.color.white};
+  cursor: pointer;
+
+  &:hover,
+  &:focus-visible {
+    filter: brightness(1.1);
+    outline: 2px solid ${({ theme }) => theme.color.red};
+  }
 `;
📝 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
export const Wrapper = styled.div`
position: relative;
display: flex;
justify-content: center;
align-items: center;
`;
export const BanArea = styled.div`
position: absolute;
top: 0px;
right: 0px;
`;
export const BanButton = styled.button`
width: 40px;
border: 1px solid ${({ theme }) => theme.color.lightgrey};
border-radius: ${({ theme }) => theme.borderRadius.primary};
background-color: ${({ theme }) => theme.color.red};
color: ${({ theme }) => theme.color.white};
`;
export const Wrapper = styled.div`
position: relative;
display: flex;
justify-content: center;
align-items: center;
`;
export const BanArea = styled.div`
position: absolute;
top: 0px;
right: 0px;
`;
export const BanButton = styled.button`
height: 40px;
width: 40px;
border: 1px solid ${({ theme }) => theme.color.lightgrey};
border-radius: ${({ theme }) => theme.borderRadius.primary};
background-color: ${({ theme }) => theme.color.red};
color: ${({ theme }) => theme.color.white};
cursor: pointer;
&:hover,
&:focus-visible {
filter: brightness(1.1);
outline: 2px solid ${({ theme }) => theme.color.red};
}
`;
🤖 Prompt for AI Agents
In src/components/admin/userCard/UserCard.styled.ts between lines 13 and 33, the
BanButton styled component lacks accessibility and usability enhancements. Add
'cursor: pointer;' to visually indicate it is clickable, specify a height to
prevent zero height rendering when content is absent, and define distinct
':hover' and ':focus-visible' state styles to provide visual feedback during
keyboard navigation and mouse hover.

Comment on lines +22 to +30
<S.BanArea
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onBan(userData.id);
}}
>
<S.BanButton>퇴출</S.BanButton>
</S.BanArea>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

onBan 콜백이 구현되지 않았습니다

현재 onBan 은 상위에서 빈 함수로 전달됩니다. 실제 정지 요청을 서버에 보내거나 모달을 띄우는 로직이 필요합니다. 미구현이면 TODO 주석이라도 남겨 명시해 주세요.

🤖 Prompt for AI Agents
In src/components/admin/userCard/UserCard.tsx around lines 22 to 30, the onBan
callback is currently a no-op passed from the parent component. Implement the
onBan function to either send a ban request to the server or open a confirmation
modal. If the implementation is pending, add a TODO comment in the code to
clearly indicate that this functionality needs to be completed.

Comment on lines +22 to +28
if (isLoading || isFetching) {
return (
<S.Spinner>
<Spinner />
</S.Spinner>
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로딩 외 에러 상태 미처리

isLoading, isFetching 외에 error(혹은 isError) 상태가 있을 경우 사용자에게 빈 화면이 노출됩니다. 에러 상태를 명시적으로 처리해 주세요.

🤖 Prompt for AI Agents
In src/components/admin/adminUserDetail/AdminUserDetail.tsx around lines 22 to
28, the current code handles loading and fetching states but does not handle
error states like error or isError. Update the component to explicitly check for
error conditions and render an appropriate error message or UI to inform the
user instead of showing a blank screen.

Comment on lines +18 to +21
const AdminUserDetail = () => {
const { userId } = useParams();
const { userData, isLoading, isFetching } = useGetUserInfo(Number(userId));

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

userId 유효성 체크 누락으로 NaN 호출 가능

useParams()가 문자열 또는 undefined를 반환할 수 있는데, 곧바로 Number()를 호출하면 NaN이 발생합니다. 훅 내부에서 NaN 을 처리하지 않으면 불필요한 API 호출이나 런타임 오류가 발생할 수 있습니다.

-  const { userData, isLoading, isFetching } = useGetUserInfo(Number(userId));
+  // guard: userId 가 없거나 숫자가 아니면 404 로 리다이렉트
+  const parsedId = Number(userId);
+  if (!userId || Number.isNaN(parsedId)) {
+    return <Navigate to="/404" replace />;
+  }
+  const { userData, isLoading, isFetching } = useGetUserInfo(parsedId);
📝 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 AdminUserDetail = () => {
const { userId } = useParams();
const { userData, isLoading, isFetching } = useGetUserInfo(Number(userId));
const AdminUserDetail = () => {
const { userId } = useParams();
// guard: userId 가 없거나 숫자가 아니면 404 로 리다이렉트
const parsedId = Number(userId);
if (!userId || Number.isNaN(parsedId)) {
return <Navigate to="/404" replace />;
}
const { userData, isLoading, isFetching } = useGetUserInfo(parsedId);
🤖 Prompt for AI Agents
In src/components/admin/adminUserDetail/AdminUserDetail.tsx around lines 18 to
21, the userId from useParams() is converted to a number without validation,
which can result in NaN if userId is undefined or not a valid number. Add a
check to ensure userId is defined and a valid number before calling
useGetUserInfo. If invalid, handle the case appropriately to prevent unnecessary
API calls or runtime errors.

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

🧹 Nitpick comments (4)
src/components/admin/adminUserDetail/AdminUserDetail.styled.ts (4)

1-1: 파일 주석 경로가 실제 위치와 불일치합니다

주석에는 src/components/admin/userDetail 로 표기되어 있으나 실제 파일 경로는 adminUserDetail 입니다. 검색-기반 도구나 IDE에서 빠르게 찾기 어렵게 만들 수 있으므로 주석을 맞춰 주세요.

-// src/components/admin/userDetail/AdminUserDetail.styled.ts
+// src/components/admin/adminUserDetail/AdminUserDetail.styled.ts

68-72: 스크롤바 의사요소에 불필요한 position·left 속성이 포함되어 있습니다

::-webkit-scrollbar 의사요소에는 position, left 속성이 적용되지 않으며 일부 브라우저에서 경고가 발생할 수 있습니다. 제거하는 편이 낫습니다.

   &::-webkit-scrollbar {
     width: 8px;
-    position: relative;
-    left: 0px;
   }

74-76: 알파값이 포함된 색상은 테마와 통일하는 것이 좋습니다

rgba(0, 0, 0, 0.2) 색상은 테마 색상과 톤이 달라 다크모드 대응에 취약합니다. theme에 투명도 색상을 추가하거나 theme.color.black 를 활용해 계산하는 방안을 고려해 주세요.


81-84: 하드코딩된 보더·라운드 값이 테마와 불일치합니다

다른 컴포넌트에서는 theme.borderRadius.large 등을 쓰는데 여기서는 직접 2px, 30px 을 사용했습니다. 통일성을 위해 theme 값을 사용하는 편이 유지보수에 유리합니다.

-  border: 2px solid #f0f0f0;
-  border-radius: 30px;
+  border: 2px solid ${({ theme }) => theme.color.grey100};
+  border-radius: ${({ theme }) => theme.borderRadius.large};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2256f and 1154cce.

📒 Files selected for processing (8)
  • src/components/admin/adminUserDetail/AdminUserDetail.styled.ts (1 hunks)
  • src/components/admin/adminUserDetail/AdminUserDetail.tsx (1 hunks)
  • src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx (1 hunks)
  • src/components/admin/userCard/UserCard.styled.ts (1 hunks)
  • src/components/common/sidebar/Sidebar.styled.ts (2 hunks)
  • src/components/common/sidebar/Sidebar.tsx (2 hunks)
  • src/pages/admin/adminUser/AdminUser.tsx (3 hunks)
  • src/routes/AdminRoutes.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx
  • src/components/common/sidebar/Sidebar.tsx
  • src/routes/AdminRoutes.tsx
  • src/components/admin/userCard/UserCard.styled.ts
  • src/pages/admin/adminUser/AdminUser.tsx
  • src/components/admin/adminUserDetail/AdminUserDetail.tsx
  • src/components/common/sidebar/Sidebar.styled.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: accessibility-test

Comment on lines +7 to +13
export const Container = styled.div`
width: 100%;
height: 800px;
margin: 6rem auto 0;
display: flex;
flex-direction: column;
`;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

고정 높이(800px)는 반응형 이슈를 유발할 수 있습니다

Container 높이를 픽스 값으로 두면 뷰포트가 작은 노트북이나 모바일 환경에서 컨텐츠가 잘리거나 스크롤이 불필요하게 중첩될 수 있습니다.

-export const Container = styled.div`
-  width: 100%;
-  height: 800px;
+export const Container = styled.div`
+  width: 100%;
+  min-height: 100vh; /* 또는 calc(100vh - 헤더높이) */
   margin: 6rem auto 0;
   display: flex;
   flex-direction: column;
 `;
📝 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
export const Container = styled.div`
width: 100%;
height: 800px;
margin: 6rem auto 0;
display: flex;
flex-direction: column;
`;
export const Container = styled.div`
width: 100%;
min-height: 100vh; /* 또는 calc(100vh - 헤더높이) */
margin: 6rem auto 0;
display: flex;
flex-direction: column;
`;
🤖 Prompt for AI Agents
In src/components/admin/adminUserDetail/AdminUserDetail.styled.ts lines 7 to 13,
the Container component uses a fixed height of 800px, which can cause
responsiveness issues on smaller screens. Replace the fixed height with a
flexible value such as min-height or use relative units like vh or percentages
to allow the container to adapt to different viewport sizes and prevent content
clipping or unnecessary scrolling.

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

🧹 Nitpick comments (15)
src/components/user/userPage/userProjectList/UserProjectList.tsx (2)

11-12: 관리자 컨텍스트 감지 로직을 더 견고하게 개선하는 것을 고려해보세요.

현재 pathname.includes('/admin')를 사용한 관리자 감지 방식은 '/admin'을 포함하지만 실제로는 관리자 라우트가 아닌 경로에서 false positive를 발생시킬 수 있습니다.

더 정확한 검사를 위해 다음과 같이 개선할 수 있습니다:

-  const isAdmin = pathname.includes('/admin');
+  const isAdmin = pathname.startsWith('/admin');

또는 라우트 상수를 활용한 방법:

-  const isAdmin = pathname.includes('/admin');
+  const isAdmin = pathname.startsWith(ROUTES.admin); // ROUTES.admin이 정의되어 있다면

10-10: 컴포넌트명과 파일명의 일관성을 맞춰주세요.

파일명은 UserProjectList.tsx이지만 export된 컴포넌트명은 UserProjects입니다. 일관성을 위해 둘 중 하나로 통일하는 것이 좋겠습니다.

다음 중 하나를 선택하여 일관성을 맞춰주세요:

-export default function UserProjects() {
+export default function UserProjectList() {

또는 파일명을 UserProjects.tsx로 변경하는 방법도 있습니다.

src/api/admin/customerService/notice.api.ts (3)

5-12: 에러 처리 및 입력 검증 개선 제안

현재 구현은 기본적인 CRUD 작업을 올바르게 수행하지만, 다음과 같은 개선사항을 고려해보세요:

  1. 더 구체적인 에러 메시지 추가
  2. 입력 매개변수 검증
export const postNotice = async (formData: WriteBody) => {
+  if (!formData.title || !formData.content) {
+    throw new Error('제목과 내용은 필수 항목입니다.');
+  }
  try {
    await httpClient.post<ApiCommonBasicType>(`/notice`, formData);
  } catch (e) {
-    console.error(e);
+    console.error('공지사항 등록 중 오류가 발생했습니다:', e);
    throw e;
  }
};

14-21: 일관성 있는 에러 처리 패턴 적용

putNotice 함수에도 동일한 개선사항을 적용하세요.

export const putNotice = async (id: string, formData: WriteBody) => {
+  if (!id) {
+    throw new Error('공지사항 ID가 필요합니다.');
+  }
+  if (!formData.title || !formData.content) {
+    throw new Error('제목과 내용은 필수 항목입니다.');
+  }
  try {
    await httpClient.put<ApiCommonBasicType>(`/notice/${id}`, formData);
  } catch (e) {
-    console.error(e);
+    console.error('공지사항 수정 중 오류가 발생했습니다:', e);
    throw e;
  }
};

23-30: 삭제 함수 에러 처리 개선

deleteNotice 함수에도 입력 검증과 구체적인 에러 메시지를 추가하세요.

export const deleteNotice = async (id: string) => {
+  if (!id) {
+    throw new Error('삭제할 공지사항 ID가 필요합니다.');
+  }
  try {
    await httpClient.delete<ApiCommonBasicType>(`/notice/${id}`);
  } catch (e) {
-    console.error(e);
+    console.error('공지사항 삭제 중 오류가 발생했습니다:', e);
    throw e;
  }
};
src/api/admin/customerService/inquiry.api.ts (4)

19-30: 문의 상세 조회 함수의 에러 처리 개선

getInquiryDetail 함수에 구체적인 에러 메시지와 입력 검증을 추가하세요.

export const getInquiryDetail = async (id: string) => {
+  if (!id) {
+    throw new Error('문의 ID가 필요합니다.');
+  }
  try {
    const response = await httpClient.get<ApiAdminInquiryDetail>(
      `/inquiry/${id}`
    );

    return response.data.data;
  } catch (e) {
-    console.error(e);
+    console.error('문의 상세 조회 중 오류가 발생했습니다:', e);
    throw e;
  }
};

32-41: 문의 답변 등록 함수 개선

postInquiryAnswer 함수에 입력 검증과 구체적인 에러 메시지를 추가하세요.

export const postInquiryAnswer = async ({ id, answer }: InquiryAnswerBody) => {
+  if (!id || !answer) {
+    throw new Error('문의 ID와 답변 내용이 모두 필요합니다.');
+  }
  try {
    await httpClient.post<ApiCommonBasicType>(`/inquiry/${id}/answer`, {
      answer,
    });
  } catch (e) {
-    console.error(e);
+    console.error('문의 답변 등록 중 오류가 발생했습니다:', e);
    throw e;
  }
};

43-52: 문의 답변 수정 함수 개선

patchInquiryAnswer 함수에도 동일한 개선사항을 적용하세요.

export const patchInquiryAnswer = async ({ id, answer }: InquiryAnswerBody) => {
+  if (!id || !answer) {
+    throw new Error('문의 ID와 답변 내용이 모두 필요합니다.');
+  }
  try {
    await httpClient.patch<ApiCommonBasicType>(`/inquiry/${id}/answer`, {
      answer,
    });
  } catch (e) {
-    console.error(e);
+    console.error('문의 답변 수정 중 오류가 발생했습니다:', e);
    throw e;
  }
};

54-61: 문의 삭제 함수 개선

deleteInquiry 함수에 입력 검증과 구체적인 에러 메시지를 추가하세요.

export const deleteInquiry = async (id: string) => {
+  if (!id) {
+    throw new Error('삭제할 문의 ID가 필요합니다.');
+  }
  try {
    await httpClient.delete<ApiCommonBasicType>(`/inquiry/${id}`);
  } catch (e) {
-    console.error(e);
+    console.error('문의 삭제 중 오류가 발생했습니다:', e);
    throw e;
  }
};
src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (2)

4-4: 인터페이스 이름의 오타를 수정해주세요

AdminSKillTagItemsProps에서 AdminSkillTagItemsProps로 수정이 필요합니다.

-interface AdminSKillTagItemsProps {
+interface AdminSkillTagItemsProps {

9-12: 인터페이스 이름 일관성을 유지해주세요

함수 매개변수 타입도 수정된 인터페이스 이름을 사용하도록 업데이트해주세요.

}: AdminSKillTagItemsProps) {
}: AdminSkillTagItemsProps) {
src/components/common/skillTagBox/SkillTagBox.tsx (1)

39-44: 불필요한 삼항 연산자를 단순화하세요.

정적 분석 도구가 지적한 대로, 불필요한 삼항 연산자를 제거할 수 있습니다.

다음과 같이 단순화할 수 있습니다:

                  $select={
-                    selectedTag?.includes(skillTagData.id) ||
-                    ((isMain &&
-                      searchFiltersSkillTag?.includes(skillTagData.id)) ||
-                    (isCreate && selectedTag?.includes(skillTagData.id))
-                      ? true
-                      : false)
+                    selectedTag?.includes(skillTagData.id) ||
+                    (isMain && searchFiltersSkillTag?.includes(skillTagData.id)) ||
+                    (isCreate && selectedTag?.includes(skillTagData.id))
                  }
src/api/admin/tag.api.ts (1)

5-12: API 함수 구조는 일관되지만 에러 처리를 개선하세요.

현재 구현은 작동하지만 다음 개선사항을 고려하세요:

  1. 프로덕션 환경에서 console.error 사용은 적절하지 않을 수 있습니다
  2. 에러 타입에 따른 구체적인 처리가 필요할 수 있습니다

공통 에러 처리 함수를 생성하여 중복을 줄일 수 있습니다:

const handleApiError = (error: unknown, operation: string) => {
  // 로깅은 환경에 따라 다르게 처리
  if (process.env.NODE_ENV === 'development') {
    console.error(`${operation} failed:`, error);
  }
  throw error;
};

export const postSkillTag = async (formData: FormData) => {
  try {
    await httpClient.post<ApiCommonBasicType>(`/skill-tag`, formData);
  } catch (e) {
    handleApiError(e, 'postSkillTag');
  }
};
src/components/admin/adminTags/AdminTagsBasic.tsx (1)

13-15: URL 기반 태그 타입 결정 로직을 더 견고하게 만드세요.

현재 구현은 작동하지만 URL 구조 변경에 취약합니다.

더 명시적인 방법을 고려해보세요:

- const witchTag: TWitchTag = pathname.includes('skill') ? 'skill' : 'position';
+ const witchTag: TWitchTag = pathname.endsWith('/skill') || pathname.includes('/skill/') 
+   ? 'skill' 
+   : 'position';

또는 라우트 매개변수를 사용하는 것이 더 좋을 수 있습니다.

src/components/admin/adminTags/AdminTagCRUD.tsx (1)

109-117: 타입 단언을 더 안전하게 개선하세요.

현재의 타입 단언은 작동하지만 더 안전한 방법을 사용할 수 있습니다.

-            state.handlePutTag({ params: formData, id: itemId } as {
-              params: T;
-              id: number;
-            });
+            state.handlePutTag({ 
+              params: formData as T, 
+              id: itemId 
+            });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1154cce and 306ae0b.

📒 Files selected for processing (39)
  • src/api/admin/customerService/inquiry.api.ts (1 hunks)
  • src/api/admin/customerService/notice.api.ts (1 hunks)
  • src/api/admin/tag.api.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagCRUD.styled.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagCRUD.tsx (1 hunks)
  • src/components/admin/adminTags/AdminTagsBasic.styled.ts (1 hunks)
  • src/components/admin/adminTags/AdminTagsBasic.tsx (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionItems.styled.ts (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionItems.tsx (1 hunks)
  • src/components/admin/adminTags/positions/AdminPositionTags.tsx (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTagItems.styled.ts (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (1 hunks)
  • src/components/admin/adminTags/skills/AdminSkillTags.tsx (1 hunks)
  • src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx (1 hunks)
  • src/components/common/positionButton/PositionButton.styled.ts (0 hunks)
  • src/components/common/positionButton/PositionButton.tsx (1 hunks)
  • src/components/common/skillTagBox/SkillTagBox.tsx (2 hunks)
  • src/components/common/skillTagBox/skillTag/SkillTag.tsx (1 hunks)
  • src/components/common/skillTagBox/skillTag/skillTagImg/SkillTagImg.tsx (1 hunks)
  • src/components/user/home/projectCardLists/cardList/CardList.tsx (0 hunks)
  • src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (1 hunks)
  • src/components/user/mypage/myProfile/editProfile/EditProfile.tsx (2 hunks)
  • src/components/user/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx (2 hunks)
  • src/components/user/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.tsx (3 hunks)
  • src/components/user/userPage/userProjectList/UserProjectList.tsx (3 hunks)
  • src/constants/admin/sidebar.ts (1 hunks)
  • src/constants/routes.ts (2 hunks)
  • src/constants/user/modalMessage.ts (1 hunks)
  • src/hooks/admin/useAdminInquiry.ts (1 hunks)
  • src/hooks/admin/useAdminNotice.ts (1 hunks)
  • src/hooks/admin/useAdminTag.ts (1 hunks)
  • src/hooks/admin/useGetAllInquiries.ts (1 hunks)
  • src/hooks/queries/keys.ts (1 hunks)
  • src/hooks/user/useSearchFilteringTags.ts (1 hunks)
  • src/models/tags.ts (2 hunks)
  • src/pages/admin/adminTags/AdminTags.tsx (0 hunks)
  • src/pages/admin/adminTags/position/AdminPositionTagsPage.tsx (1 hunks)
  • src/pages/admin/adminTags/skill/AdminSkillTagsPage.tsx (1 hunks)
  • src/routes/AdminRoutes.tsx (6 hunks)
💤 Files with no reviewable changes (3)
  • src/components/user/home/projectCardLists/cardList/CardList.tsx
  • src/components/common/positionButton/PositionButton.styled.ts
  • src/pages/admin/adminTags/AdminTags.tsx
✅ Files skipped from review due to trivial changes (15)
  • src/components/admin/adminTags/positions/AdminPositionTags.tsx
  • src/components/common/admin/sidebar/sidebarList/AdminSidebarList.tsx
  • src/components/user/projectFormComponents/projectInformationInput/ProjectInformationInput.tsx
  • src/components/admin/adminTags/skills/AdminSkillTagItems.styled.ts
  • src/hooks/admin/useAdminInquiry.ts
  • src/pages/admin/adminTags/position/AdminPositionTagsPage.tsx
  • src/components/admin/adminTags/positions/AdminPositionItems.styled.ts
  • src/hooks/admin/useAdminNotice.ts
  • src/pages/admin/adminTags/skill/AdminSkillTagsPage.tsx
  • src/components/admin/adminTags/skills/AdminSkillTags.tsx
  • src/constants/user/modalMessage.ts
  • src/constants/admin/sidebar.ts
  • src/hooks/queries/keys.ts
  • src/components/admin/adminTags/AdminTagsBasic.styled.ts
  • src/components/admin/adminTags/AdminTagCRUD.styled.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/routes/AdminRoutes.tsx
  • src/constants/routes.ts
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (2)
src/hooks/user/useSearchFilteringTags.ts (1)
  • useSearchFilteringTags (11-49)
src/hooks/user/useSaveSearchFiltering.ts (1)
  • useSaveSearchFiltering (4-10)
src/hooks/user/useSearchFilteringTags.ts (3)
src/models/tags.ts (3)
  • SkillTag (3-8)
  • PositionTag (10-14)
  • MethodTag (16-20)
src/hooks/queries/keys.ts (1)
  • Tag (73-77)
src/api/projectSearchFiltering.api.ts (2)
  • getSkillTag (4-13)
  • getPositionTag (15-23)
src/components/user/mypage/myProfile/editProfile/EditProfile.tsx (1)
src/hooks/user/useSearchFilteringTags.ts (1)
  • useSearchFilteringTags (11-49)
src/hooks/admin/useAdminTag.ts (3)
src/api/admin/tag.api.ts (6)
  • postSkillTag (5-12)
  • putSkillTag (14-27)
  • deleteSkillTag (29-36)
  • postPositionTag (38-45)
  • putPositionTag (47-60)
  • deletePositionTag (62-69)
src/hooks/queries/keys.ts (1)
  • Tag (73-77)
src/models/tags.ts (1)
  • TagFormType (34-37)
src/components/admin/adminTags/positions/AdminPositionItems.tsx (2)
src/hooks/user/useSearchFilteringTags.ts (1)
  • useSearchFilteringTags (11-49)
src/components/common/positionButton/PositionButton.tsx (1)
  • PositionButton (11-29)
src/api/admin/customerService/notice.api.ts (3)
src/models/customerService.ts (1)
  • WriteBody (59-62)
src/api/http.api.ts (1)
  • httpClient (80-80)
src/models/apiCommon.ts (1)
  • ApiCommonBasicType (6-8)
src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (1)
src/components/common/skillTagBox/SkillTagBox.tsx (1)
  • SkillTagBox (16-61)
src/components/common/skillTagBox/SkillTagBox.tsx (1)
src/hooks/user/useSearchFilteringTags.ts (1)
  • useSearchFilteringTags (11-49)
src/api/admin/customerService/inquiry.api.ts (3)
src/api/http.api.ts (1)
  • httpClient (80-80)
src/models/inquiry.ts (3)
  • ApiAdminInquiry (19-21)
  • ApiAdminInquiryDetail (23-25)
  • InquiryAnswerBody (27-30)
src/models/apiCommon.ts (1)
  • ApiCommonBasicType (6-8)
src/components/common/positionButton/PositionButton.tsx (1)
src/components/common/positionButton/PositionButton.styled.ts (1)
  • PositionButton (3-39)
src/api/admin/tag.api.ts (2)
src/api/http.api.ts (1)
  • httpClient (80-80)
src/models/apiCommon.ts (1)
  • ApiCommonBasicType (6-8)
🪛 Biome (1.9.4)
src/components/common/skillTagBox/SkillTagBox.tsx

[error] 40-44: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

🔇 Additional comments (33)
src/components/user/userPage/userProjectList/UserProjectList.tsx (2)

1-1: useLocation 훅 추가가 적절합니다.

관리자 컨텍스트 감지를 위한 useLocation 훅 import가 올바르게 추가되었습니다.


31-32: 보안 속성이 올바르게 적용되었습니다.

관리자 컨텍스트에서 새 탭으로 링크를 열 때 target="_blank"rel="noopener noreferrer" 속성이 적절히 적용되어 보안상 안전합니다.

src/hooks/admin/useGetAllInquiries.ts (1)

3-3: 임포트 경로 수정 승인

API 모듈의 경로가 올바른 케이싱으로 수정되었습니다. 표준화된 파일명 규칙에 맞는 좋은 변경사항입니다.

src/api/admin/customerService/inquiry.api.ts (1)

9-17: 전체 문의 조회 함수 승인

getAllInquiries 함수는 잘 구현되었습니다. 구체적인 에러 메시지가 포함되어 있어 디버깅에 도움이 됩니다.

src/components/common/skillTagBox/skillTag/SkillTag.tsx (1)

17-17: 이미지 캐시 무효화를 위한 updatedAt 전달이 잘 구현되었습니다.

스킬 태그 이미지의 업데이트를 반영하기 위해 updatedAt 타임스탬프를 전달하는 구현이 적절합니다.

src/models/tags.ts (2)

7-7: SkillTag 인터페이스의 updatedAt 필드 변경이 적절합니다.

이미지 캐시 무효화를 위해 createdAt에서 updatedAt으로 변경한 것이 합리적입니다.


34-37: 새로운 TagFormType 인터페이스가 잘 정의되었습니다.

관리자 태그 CRUD 작업을 위한 폼 타입이 명확하게 정의되어 있습니다.

src/components/user/mypage/myProfile/editProfile/EditProfile.tsx (2)

13-13: 훅 이름 변경이 일관되게 적용되었습니다.

useSearchFilteringSkillTag에서 useSearchFilteringTags로의 리팩토링이 올바르게 적용되었습니다.


37-37: 새로운 훅 사용법이 올바르게 적용되었습니다.

구조 분해 할당이 새로운 훅의 반환 타입과 일치하게 업데이트되었습니다.

src/components/user/home/searchFiltering/filteringContents/FilteringContents.tsx (3)

6-6: 훅 임포트 변경이 일관되게 적용되었습니다.

전체 리팩토링의 일환으로 useSearchFilteringTags 훅으로의 변경이 올바르게 반영되었습니다.


13-13: 새로운 훅 사용이 올바르게 구현되었습니다.

positionTagsDatamethodTagsData를 새로운 훅에서 올바르게 가져오고 있습니다.


21-21: 이벤트 타입이 더 구체적으로 개선되었습니다.

React.MouseEvent<HTMLElement>에서 React.MouseEvent<HTMLDivElement>로 변경하여 타입 안전성이 향상되었습니다.

src/hooks/user/useSearchFilteringTags.ts (3)

9-9: 중앙화된 쿼리 키 상수 사용이 좋은 개선입니다.

Tag 상수를 임포트하여 쿼리 키 관리를 중앙화한 것이 캐시 관리 일관성을 향상시킵니다.


11-11: 훅 이름 변경이 적절합니다.

useSearchFilteringTags로 변경하여 스킬 태그뿐만 아니라 포지션 태그와 메소드 태그도 다룬다는 것을 더 명확하게 표현합니다.


19-19: 정적 쿼리 키 사용으로 캐시 관리가 개선되었습니다.

동적 배열 키에서 정적 Tag 상수로 변경하여 React Query 캐시 관리의 일관성과 안정성이 향상되었습니다.

Also applies to: 25-25, 31-31

src/hooks/admin/useAdminTag.ts (1)

1-97: 잘 구현된 커스텀 훅입니다!

React Query 패턴을 올바르게 따르고 있으며, 각 mutation마다 적절한 에러 핸들링과 캐시 무효화가 구현되어 있습니다. 스킬 태그와 포지션 태그의 CRUD 작업을 중앙에서 관리하는 좋은 구조입니다.

src/components/common/skillTagBox/skillTag/skillTagImg/SkillTagImg.tsx (2)

6-6: 좋은 캐시 무효화 구현입니다!

updatedAt 프로퍼티를 추가하여 이미지 캐싱 문제를 해결하는 좋은 접근 방식입니다.


19-23: 이미지 캐시 무효화가 올바르게 구현되었습니다

쿼리 파라미터로 updatedAt을 사용하여 이미지가 업데이트될 때 브라우저 캐시를 무효화하는 표준적인 방법입니다.

src/components/admin/adminTags/positions/AdminPositionItems.tsx (1)

1-26: 깔끔하게 구현된 컴포넌트입니다!

useSearchFilteringTags 훅을 사용하여 데이터를 가져오고, 각 포지션 태그에 대해 클릭 핸들러를 올바르게 구현했습니다. 콜백 패턴을 적절히 사용하여 부모 컴포넌트와의 상호작용을 처리하고 있습니다.

src/components/user/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.tsx (3)

1-1: React import 추가가 적절합니다

TypeScript strict mode에서 필요한 React import를 추가했습니다.


31-36: 클릭 핸들링 로직이 잘 리팩토링되었습니다

인덱스 증가 로직을 별도의 래퍼 함수로 분리하여 코드 가독성을 향상시켰습니다.


47-47: 이벤트 핸들링이 올바르게 업데이트되었습니다

인라인 화살표 함수에서 onClickSelect prop으로 변경하여 일관성을 유지했습니다.

src/components/admin/adminTags/skills/AdminSkillTagItems.tsx (2)

13-23: 견고한 클릭 이벤트 핸들링 구현입니다

data-id 속성을 추출하기 위해 직접적인 타겟과 상위 요소 검색을 모두 사용하는 방식이 좋습니다. 이벤트 전파 중단도 적절히 처리되어 있습니다.


25-29: SkillTagBox 컴포넌트 사용이 올바릅니다

선택된 태그 ID를 selectedTag prop으로 전달하는 구조가 적절합니다.

src/components/common/skillTagBox/SkillTagBox.tsx (2)

2-2: 훅 리팩토링이 올바르게 적용되었습니다.

useSearchFilteringSkillTag에서 useSearchFilteringTags로의 변경이 새로운 태그 관리 시스템과 일치합니다.


9-17: Props 개선이 잘 적용되었습니다.

width prop을 선택적으로 만들고 기본값을 제공한 것이 컴포넌트 사용성을 향상시킵니다.

src/components/common/positionButton/PositionButton.tsx (2)

5-8: Props 및 이벤트 타입 개선이 적절합니다.

fontSize를 선택적으로 만들고 이벤트 타입을 버튼 요소에 맞게 변경한 것이 올바릅니다.


19-27: 컴포넌트 구조 개선으로 접근성이 향상되었습니다.

외부 컨테이너 div를 제거하고 버튼 요소를 직접 사용하는 것은 의미적 HTML과 접근성을 개선합니다.

src/components/admin/adminTags/AdminTagsBasic.tsx (2)

28-48: tagState 구성 객체가 잘 설계되었습니다.

각 태그 타입에 대한 설정과 핸들러를 체계적으로 구성했습니다. 제네릭 타입 사용도 적절합니다.


57-69: 조건부 렌더링에서 제네릭 타입 사용이 훌륭합니다.

FormData와 string 타입에 대한 제네릭 타입 사용이 타입 안전성을 제공합니다.

src/components/admin/adminTags/AdminTagCRUD.tsx (3)

58-76: 폼 검증 로직이 올바르게 구현되었습니다.

입력값 검증과 스킬 태그의 이미지 필수 검증이 적절합니다.


182-188: 메모리 누수 방지를 위한 정리 로직이 훌륭합니다.

Object URL 정리를 통한 메모리 관리가 잘 구현되어 있습니다.


151-158: ```shell
#!/bin/bash
rg -n "fileInputRef" src/components/admin/adminTags/AdminTagCRUD.tsx


</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 38 to 45
export const postPositionTag = async (name: string) => {
try {
await httpClient.post<ApiCommonBasicType>(`/position-tag`, { name });
} catch (e) {
console.error(e);
throw e;
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

포지션 태그 API에 입력 검증을 추가하세요.

빈 문자열이나 유효하지 않은 이름에 대한 검증이 필요합니다.

 export const postPositionTag = async (name: string) => {
+  if (!name || name.trim().length === 0) {
+    throw new Error('Tag name cannot be empty');
+  }
   try {
-    await httpClient.post<ApiCommonBasicType>(`/position-tag`, { name });
+    await httpClient.post<ApiCommonBasicType>(`/position-tag`, { name: name.trim() });
   } catch (e) {
     console.error(e);
     throw e;
   }
 };
📝 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
export const postPositionTag = async (name: string) => {
try {
await httpClient.post<ApiCommonBasicType>(`/position-tag`, { name });
} catch (e) {
console.error(e);
throw e;
}
};
export const postPositionTag = async (name: string) => {
if (!name || name.trim().length === 0) {
throw new Error('Tag name cannot be empty');
}
try {
await httpClient.post<ApiCommonBasicType>(`/position-tag`, { name: name.trim() });
} catch (e) {
console.error(e);
throw e;
}
};
🤖 Prompt for AI Agents
In src/api/admin/tag.api.ts around lines 38 to 45, the postPositionTag function
lacks input validation for the name parameter. Add validation to check if the
name is a non-empty string and meets any required format criteria before making
the HTTP POST request. If the validation fails, throw an appropriate error or
reject the input to prevent sending invalid data to the API.

queryKey: [userInfoKey.userProfile, id],
queryFn: () => getUserInfo(id),
staleTime: 1 * 60 * 1000,
enabled: isLoggedIn,
Copy link
Collaborator

Choose a reason for hiding this comment

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

위에서 number가 없을 가능성에 대해 처리하지 않고 있으니 enabled는 !!id 일 때가 맞는 것 같긴합니당

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

감사합니다

Comment on lines 7 to 15
const useGetUserInfo = (id: number) => {
const isLoggedIn = useAuthStore.getState().isLoggedIn;

const { data, isLoading, isFetching } = useQuery<ApiUserInfo>({
queryKey: [userInfoKey.userProfile, id],
queryFn: () => getUserInfo(id),
staleTime: 1 * 60 * 1000,
enabled: isLoggedIn,
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

컴포넌트에서 불러올때 재렌더되면 다시 불러오는거 아냐?

@layout-SY layout-SY merged commit b8edda1 into develop Jun 20, 2025
4 checks passed
@layout-SY layout-SY deleted the feat/#340 branch June 20, 2025 05:02
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.

회원 상세 조회 모달 구현

3 participants