Skip to content

Comments

feat: 더보기 모달, 팝업(Y/N) 모달, 스낵바 구현 #54

Merged
heeeeyong merged 11 commits intodevelopfrom
feature/popup-hooks
Jul 23, 2025
Merged

feat: 더보기 모달, 팝업(Y/N) 모달, 스낵바 구현 #54
heeeeyong merged 11 commits intodevelopfrom
feature/popup-hooks

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Jul 21, 2025

#️⃣연관된 이슈

close#23 [UI] 피드페이지 구현
close#55 [UI] 스낵바 구현

📝작업 내용

  • ConfirmModal.tsx (예/아니오 모달)
image - MoreMenu.tsx (더보기 모달) image - Snackbar.tsx (스낵바)

[구현 결과]
https://github.com/user-attachments/assets/522728a2-5295-46f3-b60a-4da06b702601

  1. Zustand 스토어를 활용해서 어떤 팝업이 열렸는지 (PopupType), 모달에 전달할 데이터 (PopupProps)를 전역에서 관리하게끔 했습니다.
  2. 각기 다른 형태별 모달 컴포넌트를 구현했습니다. (ConfirmModal, MoreMenu, SnackBar)
  3. 전역상태를 보고 PopupContainer를 통해 각 팝업들을 렌더링하게끔 했습니다.
  4. usePopupAction hook을 통한 추상화
  5. FeedDetailPage 에서 우선 팝업이 열리게끔 적용해보았습니다.
  6. MoreMenu 모달은 Overlay를 적용해 배경을 누르면 닫히도록 했습니다.
  7. 모달이 열렸을 때, 그 뒷부분은 스크롤이 불가능하게 막아두었습니다.
  8. 단, 스낵바의 경우에는 다른 활동을 가능하게 막지 않았습니다.

한개짜리 더보기 모달이 있어서 그것도 마저 구현하겠습니다. 다만 컴포넌트 재활용을 어떻게 할 수 있을지 고민을 좀 해보겠습니다...

💬리뷰 요구사항

우선, 팝업 자체가 화면 젤 위에서 렌더링 되고 있기 때문에 컴포넌트 자체를 분리해서 쓰는건 맞다고 생각해서 전역으로 팝업을 관리해야겠다고 생각했습니다. 전역에서 팝업의 isOpen 여부를 파악해 열리고 닫히게끔 처리했습니다. 컴포넌트와 비즈니스 로직의 분리, 다양한 종류의 팝업의 확장성을 고려해 전반적인 코드를 구현했습니다.

우선 이렇게 구현하는게 괜찮은 것 같은지 여러분들의 생각이 궁금합니다.
또, usePopupAction 로 훅을 따로 빼서 사용하는게 더 좋은게 맞는지 궁금합니다! 뭔가 엄청난 차이가 아닌 것 같은데 훅을 따로 또 빼려니 코드만 더 많아지는거 같아서 의문이 들었습니다.

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능

    • 사용자 검색 페이지 및 검색 결과 컴포넌트가 추가되었습니다.
    • 팝업(확인 모달, 더보기 메뉴, 스낵바) 기능이 도입되었습니다.
    • 팝업 상태 관리를 위한 전역 스토어와 커스텀 훅이 추가되었습니다.
    • 모의 사용자 데이터와 타입이 추가되어 다양한 사용자 목록 및 프로필 표시가 가능합니다.
  • 기능 개선

    • 피드, 팔로우/팔로워 리스트, 프로필 등에서 네비게이션 경로 및 UI 동작이 개선되었습니다.
    • 검색바 및 버튼 스타일이 일부 개선되었습니다.
  • 버그 수정

    • 피드 및 포스트 관련 데이터와 컴포넌트에서 postId → feedId로 일관성 있게 변경되었습니다.
  • 리팩토링

    • 타입 및 데이터 구조가 통합되어 코드 일관성이 향상되었습니다.

@heeeeyong heeeeyong self-assigned this Jul 21, 2025
@heeeeyong heeeeyong added ✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링 labels Jul 21, 2025
@vercel
Copy link

vercel bot commented Jul 21, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
thip ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 23, 2025 1:50pm

@coderabbitai
Copy link

coderabbitai bot commented Jul 21, 2025

Caution

Review failed

The pull request is closed.

"""

Walkthrough

이 변경 사항은 팝업/모달 시스템을 전역적으로 도입하고, 피드/유저 프로필 관련 타입 및 데이터 구조를 통일하며, 검색 및 피드 상세 기능을 확장합니다. 모달, 스낵바, 더보기 메뉴 컴포넌트와 팝업 상태 관리 스토어, 관련 커스텀 훅이 추가되고, 기존 컴포넌트와 라우팅, 목데이터, 타입이 이에 맞게 일괄적으로 수정되었습니다.

Changes

파일/경로 요약 변경 요약
src/components/common/Modal/ConfirmModal.tsx
src/components/common/Modal/MoreMenu.tsx
src/components/common/Modal/PopupContainer.tsx
src/components/common/Modal/Snackbar.tsx
ConfirmModal, MoreMenu, PopupContainer, Snackbar 등 팝업/모달 관련 신규 컴포넌트 추가
src/stores/usePopupStore.ts
src/hooks/usePopupActions.ts
팝업 상태 관리용 Zustand 스토어 및 팝업 액션 커스텀 훅 추가
src/App.tsx PopupContainer를 앱 전체에 통합
src/pages/feed/FeedDetailPage.tsx 피드 상세의 더보기 버튼 클릭 시 팝업(더보기 메뉴, 삭제 확인, 스낵바) 동작 로직 추가
src/pages/feed/UserSearch.tsx
src/pages/feed/UserSearchResult.tsx
유저 검색 및 검색 결과용 신규 컴포넌트 추가
src/pages/index.tsx "feed/search" 경로에 UserSearch 라우트 추가
src/components/feed/FollowList.tsx
src/components/feed/MyFollower.tsx
"더보기" 클릭 시 이동 경로를 '/feed/...'에서 '/follow/...'로 변경
src/components/feed/UserProfileItem.tsx URL 기반 분기 제거, type prop 도입, 네비게이션 경로 수정, 타입 import로 통일
src/types/user.ts UserProfileType, UserProfileItemProps 타입 신설
src/data/userData.ts UserProfileItemProps 기반 mockUserList 목데이터 신규 파일로 분리
src/pages/feed/FollowerListPage.tsx 목데이터 외부화, UserProfileItem type prop 명시적 전달
src/pages/feed/Feed.tsx 검색 버튼 클릭 시 '/feed/search'로 이동하는 핸들러 추가
src/components/common/Post/PostBody.tsx
src/components/common/Post/PostFooter.tsx
src/components/feed/TotalFeed.tsx
src/types/post.ts
src/data/postData.ts
postId → feedId로 프로퍼티명 및 타입 일괄 변경, 관련 컴포넌트 및 목데이터, 타입 수정
src/components/search/SearchBar.tsx 내부 패딩 축소, caret 색상 네온그린으로 변경

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant FeedDetailPage
    participant PopupContainer
    participant usePopupStore
    participant MoreMenu
    participant ConfirmModal
    participant Snackbar

    User->>FeedDetailPage: 더보기 버튼 클릭
    FeedDetailPage->>usePopupStore: openPopup('moremenu', {...})
    usePopupStore-->>PopupContainer: popupType='moremenu', isOpen=true
    PopupContainer->>MoreMenu: 렌더링
    User->>MoreMenu: 삭제하기 클릭
    MoreMenu->>usePopupStore: openPopup('confirm-modal', {...})
    usePopupStore-->>PopupContainer: popupType='confirm-modal', isOpen=true
    PopupContainer->>ConfirmModal: 렌더링
    User->>ConfirmModal: 예 클릭
    ConfirmModal->>usePopupStore: closePopup()
    ConfirmModal->>usePopupStore: openPopup('snackbar', {...})
    usePopupStore-->>PopupContainer: popupType='snackbar', isOpen=true
    PopupContainer->>Snackbar: 렌더링
Loading

Estimated code review effort

4 (~90분)

Possibly related PRs

  • THIP-TextHip/THIP-Web#48: FollowList 및 MyFollower 컴포넌트의 네비게이션 경로와 핸들러를 수정한 PR로, 본 PR의 동일 파일과 경로 변경과 직접적으로 연관됨.
  • THIP-TextHip/THIP-Web#53: UserProfileItem.tsx에서 pathname 기반 분기 제거 및 type prop 도입 등, 본 PR과 동일한 구조적 변경을 포함함.

Poem

🐇
팝업이 번쩍, 모달이 뿅!
스낵바도 살짝, 알림이 쏙!
팔로우, 피드, 검색까지
타입도 정리, 경로도 착착
토끼는 기뻐 깡총깡총,
새 UI에서 춤을 춰요!
🌱✨
"""


📜 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 ee1b41b and a8a35e6.

📒 Files selected for processing (1)
  • src/components/common/Modal/ConfirmModal.tsx (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

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

🧹 Nitpick comments (11)
src/data/userData.ts (1)

3-38: mock 데이터 구조를 개선하는 것을 고려해보세요.

현재 구조는 올바르지만 개발 경험 향상을 위해 다음 사항들을 고려해보세요:

  • placeholder URL 대신 실제 이미지나 더 현실적인 mock 이미지 사용
  • 더 다양한 사용자 데이터 패턴 추가
  • 데이터 일관성 확보 (일부 항목에만 isFollowed 속성이 있음)
 export const mockUserList: UserProfileItemProps[] = [
   {
-    profileImgUrl: 'https://placehold.co/36x36',
+    profileImgUrl: 'https://images.unsplash.com/photo-1494790108755-2616b68073c5?w=36&h=36&fit=crop&crop=face',
     userName: 'ThipOther',
     userTitle: '칭호칭호',
     titleColor: '#FF8BAC',
     followerCount: 15,
     userId: 9,
+    isFollowed: false,
   },
src/pages/feed/FeedDetailPage.tsx (1)

22-36: modal 통합이 잘 구현되었지만 실제 비즈니스 로직이 필요합니다.

중첩 modal 패턴(더보기 → 확인 dialog)이 좋은 UX를 제공합니다. 다만 현재 console.log로 처리된 실제 수정/삭제 로직 구현이 필요합니다.

실제 수정/삭제 API 호출 로직을 구현하는데 도움이 필요하시면 말씀해 주세요.

src/pages/feed/UserSearchResult.tsx (3)

12-15: 함수 로직을 단순화하세요.

isEmptySearchedUserList 함수가 불필요하게 복잡합니다. 단순한 boolean 체크는 직접 인라인으로 처리할 수 있습니다.

다음과 같이 수정하세요:

-  const isEmptySearchedUserList = () => {
-    if (searchedUserList.length === 0) return true;
-    else return false;
-  };

그리고 22행을 다음과 같이 변경하세요:

-        {isEmptySearchedUserList() ? (
+        {searchedUserList.length === 0 ? (

20-20: 불필요한 빈 JSX Fragment를 제거하세요.

searching 타입일 때 빈 fragment(<></>)를 렌더링하는 것보다 null을 반환하는 것이 더 명확합니다.

-        {type === 'searching' ? <></> : <ResultHeader>전체 {searchedUserList.length}</ResultHeader>}
+        {type === 'searched' && <ResultHeader>전체 {searchedUserList.length}</ResultHeader>}

59-65: EmptyWrapper에 콘텐츠 추가를 고려하세요.

빈 상태를 나타내는 EmptyWrapper가 완전히 비어있습니다. 사용자 경험을 위해 "검색 결과가 없습니다" 같은 메시지를 추가하는 것을 고려해보세요.

빈 상태에 대한 UI를 구현해드릴까요?

src/pages/feed/UserSearch.tsx (2)

16-17: 검색 상태 관리를 단순화하세요.

isSearchingisSearched 두 개의 boolean 상태가 혼란을 줄 수 있습니다. 하나의 enum이나 union type으로 상태를 관리하는 것을 고려해보세요.

type SearchState = 'idle' | 'searching' | 'searched';
const [searchState, setSearchState] = useState<SearchState>('idle');

86-88: 자체 닫는 태그를 사용하세요.

UserSearchResult 컴포넌트가 children을 받지 않으므로 자체 닫는 태그를 사용하는 것이 좋습니다.

-              <UserSearchResult
-                type={'searched'}
-                searchedUserList={mockUserList}
-              ></UserSearchResult>
+              <UserSearchResult
+                type="searched"
+                searchedUserList={mockUserList}
+              />

동일하게 다른 UserSearchResult도 수정하세요.

Also applies to: 90-93

src/components/common/Modal/ModalContainer.tsx (1)

23-25: 타입 캐스팅을 개선하세요.

as 키워드를 사용한 타입 캐스팅은 타입 안전성을 해칠 수 있습니다. 타입 가드나 더 안전한 방법을 고려해보세요.

타입 가드를 사용하여 더 안전하게 처리할 수 있습니다:

const renderModal = () => {
  switch (modalType) {
    case 'confirm':
      const confirmProps = modalProps as ConfirmModalProps;
      return <ConfirmModal {...confirmProps} onClose={closeModal} />;
    case 'moremenu':
      const moreProps = modalProps as MoreMenuProps;
      return <MoreMenu {...moreProps} onClose={closeModal} />;
    default:
      return null;
  }
};

또는 Zustand 스토어에서 타입을 더 엄격하게 정의하는 것을 고려해보세요.

src/components/common/Modal/ConfirmModal.tsx (2)

8-9: 클래스명 대신 styled components를 사용하세요.

.title.disc 클래스명을 사용하는 대신 별도의 styled component로 분리하는 것이 일관성 있고 유지보수하기 좋습니다.

const Title = styled.div`
  color: ${colors.white};
  font-size: ${typography.fontSize.lg};
  font-weight: ${typography.fontWeight.medium};
  line-height: 24px;
`;

const Description = styled.div`
  color: ${colors.white};
  font-size: ${typography.fontSize.sm};
  font-weight: ${typography.fontWeight.regular};
  line-height: normal;
`;

그리고 JSX에서:

-      <div className="title">{title}</div>
-      <div className="disc">{disc}</div>
+      <Title>{title}</Title>
+      <Description>{disc}</Description>

25-26: 고정 크기 대신 반응형 크기를 고려하세요.

모달의 너비와 높이가 고정되어 있습니다. 콘텐츠 길이에 따라 높이가 조정되거나, 다양한 화면 크기에 대응할 수 있도록 개선하는 것을 고려해보세요.

  width: 320px;
- height: 182px;
+ min-height: 182px;
  padding: 20px;
src/stores/useModalStore.ts (1)

6-17: 모달별 Props 인터페이스 분리

각 모달의 props를 별도 인터페이스로 정의한 것은 타입 안전성과 유지보수성에 도움이 됩니다.

ConfirmModalPropsdisc 속성명을 description으로 변경하는 것을 고려해보세요. 더 명확한 의미 전달이 가능합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec2d34 and 6168da1.

📒 Files selected for processing (18)
  • src/App.tsx (1 hunks)
  • src/components/common/Modal/ConfirmModal.tsx (1 hunks)
  • src/components/common/Modal/ModalContainer.tsx (1 hunks)
  • src/components/common/Modal/MoreMenu.tsx (1 hunks)
  • src/components/feed/FollowList.tsx (1 hunks)
  • src/components/feed/MyFollower.tsx (1 hunks)
  • src/components/feed/UserProfileItem.tsx (3 hunks)
  • src/components/search/SearchBar.tsx (2 hunks)
  • src/data/userData.ts (1 hunks)
  • src/hooks/useModal.ts (1 hunks)
  • src/pages/feed/Feed.tsx (2 hunks)
  • src/pages/feed/FeedDetailPage.tsx (1 hunks)
  • src/pages/feed/FollowerListPage.tsx (2 hunks)
  • src/pages/feed/UserSearch.tsx (1 hunks)
  • src/pages/feed/UserSearchResult.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/stores/useModalStore.ts (1 hunks)
  • src/types/user.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/data/userData.ts (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/pages/feed/FollowerListPage.tsx (2)
src/types/user.ts (1)
  • UserProfileType (1-1)
src/data/userData.ts (1)
  • mockUserList (3-38)
src/pages/feed/UserSearch.tsx (3)
src/pages/feed/UserSearchResult.tsx (1)
  • UserSearchResult (11-37)
src/data/userData.ts (1)
  • mockUserList (3-38)
src/styles/global/global.ts (1)
  • colors (4-53)
src/hooks/useModal.ts (1)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/pages/feed/FeedDetailPage.tsx (1)
src/stores/useModalStore.ts (1)
  • useModalStore (27-33)
src/pages/feed/UserSearchResult.tsx (2)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/styles/global/global.ts (2)
  • typography (56-76)
  • colors (4-53)
src/components/common/Modal/ModalContainer.tsx (2)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/components/common/Wrapper.tsx (1)
  • Wrapper (3-14)
src/components/feed/UserProfileItem.tsx (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/components/common/Modal/MoreMenu.tsx (2)
src/stores/useModalStore.ts (1)
  • MoreMenuProps (13-17)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
🔇 Additional comments (31)
src/components/feed/FollowList.tsx (1)

35-35: 라우터 설정에 새로운 경로 등록 여부 확인 필요

해당 컴포넌트에서 라우팅 경로가 다음과 같이 변경되었습니다:

-navigate('/feed/followlist');
+navigate('/follow/followlist');
  • 변경 파일: src/components/feed/FollowList.tsx (라인 35)
  • 스크립트 검색 결과, 라우터 설정 파일 내에 /follow/followlist 경로가 등록된 부분이 발견되지 않음

라우터 설정(App.tsx 또는 별도 라우터 구성 파일)에서 /follow/followlist 경로가 제대로 정의되었는지 수동으로 확인 부탁드립니다.

src/components/feed/MyFollower.tsx (1)

73-73: 라우팅 경로 변경이 일관성 있게 적용되었습니다.

FollowList 컴포넌트와 마찬가지로 /feed/followerlist에서 /follow/followerlist로의 경로 변경이 올바르게 적용되었습니다.

src/components/search/SearchBar.tsx (2)

56-56: 검색바 패딩 조정이 적절합니다.

16px 20px에서 8px 12px로 패딩을 줄여 더욱 컴팩트한 UI를 구현했습니다. 사용자 검색 기능에 적합한 조정입니다.


78-78: 캐럿 색상 명시로 사용자 경험이 개선되었습니다.

네온 그린 색상을 명시적으로 설정하여 입력 시 커서의 가시성과 일관성을 향상시켰습니다.

src/App.tsx (1)

4-4: 모달 시스템 통합이 올바른 아키텍처로 구현되었습니다.

앱의 최상위 레벨에 ModalContainer를 배치하여 모달이 UI 계층의 최상단에서 렌더링되도록 하는 좋은 아키텍처 결정입니다. 전역 모달 관리를 위한 깔끔한 구현입니다.

Also applies to: 11-11

src/pages/index.tsx (1)

23-23: UserSearch 컴포넌트 존재 및 구현 확인

src/pages/feed/UserSearch.tsx 파일이 정상적으로 존재하며, NavBar, TitleHeader, SearchBar 등 주요 UI 요소와 상태 관리 훅이 구현되어 있습니다.
/feed/search 경로 추가를 승인합니다.

  • 확인된 위치:
    • src/pages/feed/UserSearch.tsx (기본 구조 및 주요 의존성 임포트 확인)
src/pages/feed/Feed.tsx (4)

10-10: import 추가가 적절합니다.

useNavigate hook을 올바르게 import했습니다.


21-21: hook 사용이 올바릅니다.

useNavigate hook을 컴포넌트 내에서 적절하게 사용했습니다.


24-26: 네비게이션 핸들러 구현이 깔끔합니다.

검색 페이지로의 네비게이션 로직이 명확하고 간단합니다.


34-34: MainHeader에 이벤트 핸들러 전달이 적절합니다.

leftButtonClick prop으로 핸들러를 올바르게 전달했습니다.

src/data/userData.ts (1)

1-2: 타입 import가 올바릅니다.

UserProfileItemProps 타입을 적절하게 import했습니다.

src/pages/feed/FollowerListPage.tsx (2)

6-7: 외부 데이터 소스 사용으로 코드 구조가 개선되었습니다.

mock 데이터를 별도 파일로 분리하고 적절한 타입을 import한 것이 좋습니다.


24-30: UserProfileItem에 type prop 전달이 적절합니다.

타입 정보를 명시적으로 전달하는 것이 좋은 패턴입니다. 다만 타입 단언 대신 위에서 제안한 타입 가드를 사용하면 더 안전합니다.

src/hooks/useModal.ts (2)

1-3: 적절한 의존성 import입니다.

modal store와 타입을 올바르게 import했습니다.


4-20: 훌륭한 추상화 패턴입니다.

이 custom hook은 다음과 같은 장점을 제공합니다:

  • modal store의 복잡성을 캡슐화
  • 타입 안전한 API 제공
  • 각 modal 타입에 대한 전용 메서드 제공
  • 재사용 가능한 깔끔한 인터페이스

modal 관리를 위한 우수한 아키텍처 결정입니다.

src/pages/feed/FeedDetailPage.tsx (2)

11-11: modal store import가 적절합니다.

modal 상태 관리를 위한 store를 올바르게 import했습니다.


16-16: store hook 사용이 올바릅니다.

useModalStore에서 필요한 함수만 구조 분해 할당으로 가져왔습니다.

src/types/user.ts (1)

1-13: 타입 정의가 잘 구성되어 있습니다.

사용자 프로필 관련 타입들이 명확하게 정의되어 있고, 필수/선택적 필드가 적절히 구분되어 있습니다. 타입 안전성을 제공하는 좋은 구조입니다.

src/components/common/Modal/ModalContainer.tsx (2)

11-16: 스크롤 제어 로직이 적절합니다.

모달이 열릴 때 배경 스크롤을 비활성화하고, cleanup 함수로 정리하는 로직이 잘 구현되어 있습니다. 사용자 경험을 개선하는 좋은 구현입니다.


47-47: 배경색 투명도를 확인하세요.

배경색이 rgba(18, 18, 18, 0.1)로 매우 투명합니다. 일반적으로 모달 오버레이는 더 어둡게 설정됩니다. 의도된 디자인인지 확인해보세요.

일반적인 모달 오버레이는 0.3~0.6 정도의 알파값을 사용합니다. 현재 0.1은 매우 투명해서 모달이 잘 보이지 않을 수 있습니다.

src/components/common/Modal/ConfirmModal.tsx (1)

53-65: Button 컴포넌트가 잘 구성되어 있습니다.

variant prop을 통해 버튼 스타일을 분기하는 로직이 명확하고, 접근성을 위한 cursor pointer도 적절히 설정되어 있습니다.

src/components/feed/UserProfileItem.tsx (5)

1-1: useLocation 제거로 인한 의존성 감소

useLocation 훅 제거로 컴포넌트의 라우팅 의존성이 줄어들었습니다. 이는 컴포넌트의 재사용성을 향상시키는 좋은 변경사항입니다.


5-5: 타입 정의 중앙화

타입 정의를 @/types/user로 중앙화한 것은 타입 일관성과 유지보수성 측면에서 좋은 접근입니다.


16-16: 기본값 설정이 적절함

type prop에 'followlist' 기본값을 설정한 것은 하위 호환성을 보장하는 좋은 방법입니다.


48-58: 조건부 렌더링 로직 개선

pathname 기반 조건부 렌더링에서 type prop 기반으로 변경한 것은 컴포넌트의 결합도를 낮추고 테스트하기 쉽게 만드는 좋은 리팩터링입니다.

팔로워 리스트 버튼에서 클릭 핸들러가 제거된 것으로 보이는데, 이것이 의도된 변경인지 확인해주세요.


22-22: 라우팅 경로 일관성 확인 및 설정 검토 필요

전체 navigate 호출이 모두 /otherfeed/${userId}로 업데이트되었으며, 기존 /feed/${userId} 호출은 더 이상 존재하지 않습니다.
다음 파일에서 변경이 반영된 것을 확인했습니다:

  • src/components/feed/UserProfileItem.tsx
  • src/components/feed/FollowList.tsx
  • src/components/common/Post/PostHeader.tsx

라우터 설정 파일에 /otherfeed/:userId 경로가 올바르게 등록되어 있는지도 함께 검토 부탁드립니다.

src/components/common/Modal/MoreMenu.tsx (3)

5-18: 컴포넌트 구조가 잘 설계됨

모달의 기본 구조가 올바르게 구현되어 있습니다:

  • Overlay 클릭 시 모달 닫기
  • Container에서 이벤트 전파 방지
  • 명확한 콜백 처리

30-46: Container 스타일링이 적절함

모바일 환경을 고려한 반응형 디자인과 적절한 z-index 설정이 잘 되어 있습니다. 하단에서 올라오는 모달 패턴도 모바일 UX에 적합합니다.


48-67: Button 컴포넌트의 variant 기반 스타일링

variant prop을 사용한 조건부 스타일링과 first/last-of-type 선택자를 활용한 패딩 처리가 깔끔하게 구현되었습니다.

src/stores/useModalStore.ts (2)

3-3: ModalType 정의가 명확함

Union type으로 모달 타입을 정의한 것은 타입 안전성을 보장하는 좋은 접근입니다.


27-33: Store 구현이 간단하고 명확함

Zustand를 사용한 store 구현이 깔끔하고 이해하기 쉽습니다. 초기 상태 설정과 메서드 구현이 적절합니다.

const FollowerListPage = () => {
const navigate = useNavigate();
const { type } = useParams();
const { type } = useParams<{ type: UserProfileType }>();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

useParams 타입 안전성을 개선하세요.

현재 구현에서 type이 undefined일 가능성을 고려해야 합니다.

- const { type } = useParams<{ type: UserProfileType }>();
+ const { type } = useParams<{ type: UserProfileType }>();
+ 
+ if (!type || (type !== 'followerlist' && type !== 'followlist')) {
+   // 에러 처리 또는 기본값 설정
+   return <div>잘못된 페이지 접근입니다.</div>;
+ }
🤖 Prompt for AI Agents
In src/pages/feed/FollowerListPage.tsx at line 11, the useParams hook
destructures `type` without handling the possibility that `type` may be
undefined. Update the code to safely handle or check for undefined `type` to
improve type safety, such as by adding a conditional check or providing a
default value before using `type`.

Comment on lines +83 to +95
(
{isSearched ? (
<UserSearchResult
type={'searched'}
searchedUserList={mockUserList}
></UserSearchResult>
) : (
<UserSearchResult
type={'searching'}
searchedUserList={mockUserList}
></UserSearchResult>
)}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

JSX에서 불필요한 괄호를 제거하세요.

83행과 95행에 불필요한 괄호 (, )가 있습니다. 이것은 문법 오류를 일으킬 수 있습니다.

        {isSearching ? (
-          <>
-            (
            {isSearched ? (
              <UserSearchResult
                type={'searched'}
                searchedUserList={mockUserList}
              ></UserSearchResult>
            ) : (
              <UserSearchResult
                type={'searching'}
                searchedUserList={mockUserList}
              ></UserSearchResult>
            )}
-            )
-          </>
        ) : (

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

🤖 Prompt for AI Agents
In src/pages/feed/UserSearch.tsx between lines 83 and 95, remove the unnecessary
parentheses surrounding the JSX expression. These extra parentheses are not
needed in JSX and can cause syntax errors. Simply delete the opening parenthesis
before the conditional and the closing parenthesis after it to fix the issue.

{isSearched ? (
<UserSearchResult
type={'searched'}
searchedUserList={mockUserList}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

실제 검색 결과를 반영하세요.

현재 검색 결과와 상관없이 항상 mockUserList를 사용하고 있습니다. 실제 검색 기능이 구현될 때까지 최소한 검색어에 따른 필터링이라도 적용하는 것을 고려해보세요.

검색어에 따른 필터링 로직을 추가해보세요:

const filteredUsers = mockUserList.filter(user => 
  user.userName.toLowerCase().includes(searchTerm.toLowerCase())
);

그리고 mockUserList 대신 filteredUsers를 사용하세요.

🤖 Prompt for AI Agents
In src/pages/feed/UserSearch.tsx at line 87, the component is currently passing
the unfiltered mockUserList as the searchedUserList prop, ignoring the actual
search term. To fix this, implement a filtering step that creates a
filteredUsers array by filtering mockUserList based on whether each user's
userName includes the searchTerm (case-insensitive). Then, replace the
searchedUserList prop value from mockUserList to filteredUsers so the displayed
list reflects the current search input.

<UserProfileItem
key={user.userId}
{...user}
type="followerlist"
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

하드코딩된 type 값을 확인하세요.

UserProfileItemtype="followerlist"가 하드코딩되어 있습니다. 이것이 의도된 동작인지 확인이 필요합니다.

다음 스크립트를 실행하여 UserProfileItem의 type prop 사용법을 확인하세요:


🏁 Script executed:

#!/bin/bash
# UserProfileItem 컴포넌트에서 type prop이 어떻게 사용되는지 확인
ast-grep --pattern 'type?: UserProfileType'
rg -A 10 -B 5 'type.*followerlist|followlist'

Length of output: 6978


UserSearchResult.tsx – UserProfileItem type 하드코딩 수정 요청

검색 결과 페이지(src/pages/feed/UserSearchResult.tsx)에서 UserProfileItem의 type'followerlist'로 고정되어 있어, 팔로워 수를 보여주는 UI가 렌더링됩니다. 하지만 사용자 검색 결과에서는 팔로우/언팔로우 버튼이 필요한 경우가 많으므로, 아래 위치를 검토하여 적절한 타입('followlist')으로 변경하는 것이 맞는지 확인 부탁드립니다.

• 파일: src/pages/feed/UserSearchResult.tsx
• 라인: 29

- <UserProfileItem
-   key={user.userId}
-   {...user}
-   type="followerlist"
-   isLast={index === searchedUserList.length - 1}
- />
+ <UserProfileItem
+   key={user.userId}
+   {...user}
+   type="followlist"
+   isLast={index === searchedUserList.length - 1}
+ />
📝 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
type="followerlist"
<UserProfileItem
key={user.userId}
{...user}
type="followlist"
isLast={index === searchedUserList.length - 1}
/>
🤖 Prompt for AI Agents
In src/pages/feed/UserSearchResult.tsx at line 29, the UserProfileItem
component's type prop is hardcoded as 'followerlist', which causes the UI to
show follower counts instead of follow/unfollow buttons. Change the type prop
value from 'followerlist' to 'followlist' to enable the correct UI for user
search results that require follow/unfollow functionality.

Comment on lines 19 to 25
interface ModalState {
modalType: ModalType;
modalProps?: ConfirmModalProps | MoreMenuProps;
isModalOpen: boolean;
openModal: (type: ModalType, props?: ConfirmModalProps | MoreMenuProps) => void;
closeModal: () => void;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

modalProps 타입 안전성 개선 필요

현재 modalPropsConfirmModalProps | MoreMenuProps로 정의되어 있어 타입 추론이 어렵습니다. 더 타입 안전한 구조를 고려해보세요.

다음과 같은 구조를 제안합니다:

 interface ModalState {
   modalType: ModalType;
-  modalProps?: ConfirmModalProps | MoreMenuProps;
+  modalProps?: {
+    confirm?: ConfirmModalProps;
+    moremenu?: MoreMenuProps;
+  };
   isModalOpen: boolean;
-  openModal: (type: ModalType, props?: ConfirmModalProps | MoreMenuProps) => void;
+  openModal: <T extends ModalType>(
+    type: T,
+    props?: T extends 'confirm' ? ConfirmModalProps : T extends 'moremenu' ? MoreMenuProps : never
+  ) => void;
   closeModal: () => void;
 }
📝 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
interface ModalState {
modalType: ModalType;
modalProps?: ConfirmModalProps | MoreMenuProps;
isModalOpen: boolean;
openModal: (type: ModalType, props?: ConfirmModalProps | MoreMenuProps) => void;
closeModal: () => void;
}
interface ModalState {
modalType: ModalType;
modalProps?: {
confirm?: ConfirmModalProps;
moremenu?: MoreMenuProps;
};
isModalOpen: boolean;
openModal: <T extends ModalType>(
type: T,
props?: T extends 'confirm'
? ConfirmModalProps
: T extends 'moremenu'
? MoreMenuProps
: never
) => void;
closeModal: () => void;
}
🤖 Prompt for AI Agents
In src/stores/useModalStore.ts around lines 19 to 25, the modalProps property is
currently typed as a union of ConfirmModalProps and MoreMenuProps, which reduces
type safety and makes type inference difficult. To fix this, refactor modalProps
to be a discriminated union or use a generic type parameter keyed by modalType,
so that modalProps' type is strictly associated with the modalType. This will
improve type safety and enable better type inference when using openModal.

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

🧹 Nitpick comments (12)
src/components/feed/FollowList.tsx (1)

7-22: 중앙화된 모의 데이터 사용을 고려해보세요.

다른 컴포넌트들이 src/data/userData.ts의 중앙화된 모의 데이터를 사용하는 것으로 보이는데, 이 컴포넌트는 여전히 인라인 데이터를 사용하고 있습니다. 일관성을 위해 중앙화된 데이터 소스 사용을 검토해보세요.

-const followerData = {
-  followers: [
-    { userId: 1, src: 'https://placehold.co/36x36', username: 'user1' },
-    // ... 기타 데이터
-  ],
-};
+import { mockUserList } from '../../data/userData';

+const followerData = {
+  followers: mockUserList,
+};
src/components/feed/MyFollower.tsx (1)

63-70: 모의 데이터 중앙화를 고려해보세요.

일관성을 위해 인라인 팔로워 데이터를 중앙화된 데이터 소스로 이동하는 것을 검토해보세요.

src/pages/feed/FeedDetailPage.tsx (2)

16-16: 스토어 사용 방식 확인

openModal 함수만 구조분해할당으로 가져왔는데, closeModal도 필요할 수 있습니다.


22-36: 모달 중첩 로직 구현 완료

더보기 메뉴에서 확인 모달로 이어지는 중첩 모달 패턴이 잘 구현되었습니다. 하지만 프로덕션 코드에서는 console.log 대신 실제 비즈니스 로직이 필요합니다.

실제 구현을 위한 수정 제안:

  const handleMoreClick = () => {
    openModal('moremenu', {
      onEdit: () => {
-       console.log('수정하기 클릭');
+       // TODO: 피드 수정 페이지로 네비게이션
+       navigate(`/feed/edit/${feedId}`);
      },
      onDelete: () => {
-       console.log('삭제하기 클릭');
        openModal('confirm', {
          title: '이 피드를 삭제하시겠어요?',
          disc: '삭제 후에는 되돌릴 수 없어요',
-         onConfirm: () => console.log('확인 클릭'),
+         onConfirm: () => {
+           // TODO: 피드 삭제 API 호출
+           deleteFeed(feedId);
+         },
        });
      },
    });
  };
src/components/common/Modal/ConfirmModal.tsx (1)

5-5: prop 이름 개선 권장

disc prop 이름보다는 description이 더 명확하고 일반적입니다. 코드의 가독성을 높이기 위해 변경을 고려해보세요.

src/pages/feed/UserSearchResult.tsx (2)

12-15: 함수 단순화 가능

isEmptySearchedUserList 함수를 더 간단하게 작성할 수 있습니다.

-  const isEmptySearchedUserList = () => {
-    if (searchedUserList.length === 0) return true;
-    else return false;
-  };
+  const isEmptySearchedUserList = searchedUserList.length === 0;

또는 조건문에서 직접 사용:

-        {isEmptySearchedUserList() ? (
+        {searchedUserList.length === 0 ? (

22-24: 빈 상태 개선 제안

현재 빈 상태가 완전히 비어있어 사용자에게 혼란을 줄 수 있습니다. 적절한 메시지나 아이콘을 추가하는 것을 고려해보세요.

-          <EmptyWrapper></EmptyWrapper>
+          <EmptyWrapper>
+            <div>검색 결과가 없습니다.</div>
+          </EmptyWrapper>
src/types/user.ts (1)

1-1: 검색 결과용 타입 추가 고려

현재 UserProfileType에 검색 결과 컨텍스트를 위한 타입이 없어서, 다른 컴포넌트에서 의미적으로 맞지 않는 'followerlist'를 사용하고 있습니다.

-export type UserProfileType = 'followlist' | 'followerlist';
+export type UserProfileType = 'followlist' | 'followerlist' | 'searchresult';
src/pages/feed/UserSearch.tsx (1)

19-25: 하드코딩된 초기값 개선 고려

최근 검색어가 하드코딩되어 있습니다. 실제 구현에서는 로컬 스토리지나 서버에서 가져오는 것을 고려해보세요.

-  const [recentSearches, setRecentSearches] = useState<string[]>([
-    '닉네임',
-    '작가',
-    '하위',
-    'Thip',
-    '책벌레',
-  ]);
+  const [recentSearches, setRecentSearches] = useState<string[]>(() => {
+    const saved = localStorage.getItem('recentUserSearches');
+    return saved ? JSON.parse(saved) : [];
+  });
src/stores/useModalStore.ts (2)

8-8: 오타 수정 필요: 'disc' → 'description'

disc 속성명이 축약되어 있어 가독성이 떨어집니다. 명확한 의미 전달을 위해 description으로 변경하는 것을 권장합니다.

-  disc: string;
+  description: string;

21-21: 타입 정의 개선: optional로 명확하게 표현

modalProps가 선택적 속성임을 명확히 하기 위해 타입 정의를 개선할 수 있습니다.

-  modalProps?: ConfirmModalProps | MoreMenuProps;
+  modalProps?: ConfirmModalProps | MoreMenuProps | undefined;
src/components/common/Modal/MoreMenu.tsx (1)

5-18: 접근성 개선 제안: 키보드 이벤트 처리 추가

사용자 경험 향상을 위해 ESC 키로 모달을 닫을 수 있는 기능을 추가하는 것을 권장합니다.

const MoreMenu = ({ onEdit, onDelete, onClose }: MoreMenuProps) => {
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        onClose?.();
      }
    };
    
    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [onClose]);
  
  // ... rest of component
};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec2d34 and 6168da1.

📒 Files selected for processing (18)
  • src/App.tsx (1 hunks)
  • src/components/common/Modal/ConfirmModal.tsx (1 hunks)
  • src/components/common/Modal/ModalContainer.tsx (1 hunks)
  • src/components/common/Modal/MoreMenu.tsx (1 hunks)
  • src/components/feed/FollowList.tsx (1 hunks)
  • src/components/feed/MyFollower.tsx (1 hunks)
  • src/components/feed/UserProfileItem.tsx (3 hunks)
  • src/components/search/SearchBar.tsx (2 hunks)
  • src/data/userData.ts (1 hunks)
  • src/hooks/useModal.ts (1 hunks)
  • src/pages/feed/Feed.tsx (2 hunks)
  • src/pages/feed/FeedDetailPage.tsx (1 hunks)
  • src/pages/feed/FollowerListPage.tsx (2 hunks)
  • src/pages/feed/UserSearch.tsx (1 hunks)
  • src/pages/feed/UserSearchResult.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/stores/useModalStore.ts (1 hunks)
  • src/types/user.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/pages/feed/FollowerListPage.tsx (2)
src/types/user.ts (1)
  • UserProfileType (1-1)
src/data/userData.ts (1)
  • mockUserList (3-38)
src/hooks/useModal.ts (1)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/components/common/Modal/ConfirmModal.tsx (2)
src/stores/useModalStore.ts (1)
  • ConfirmModalProps (6-11)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/data/userData.ts (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/pages/feed/FeedDetailPage.tsx (1)
src/stores/useModalStore.ts (1)
  • useModalStore (27-33)
src/pages/feed/UserSearch.tsx (3)
src/pages/feed/UserSearchResult.tsx (1)
  • UserSearchResult (11-37)
src/data/userData.ts (1)
  • mockUserList (3-38)
src/styles/global/global.ts (1)
  • colors (4-53)
src/components/feed/UserProfileItem.tsx (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/pages/feed/UserSearchResult.tsx (2)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/styles/global/global.ts (2)
  • typography (56-76)
  • colors (4-53)
src/components/common/Modal/ModalContainer.tsx (2)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/components/common/Wrapper.tsx (1)
  • Wrapper (3-14)
🔇 Additional comments (27)
src/components/feed/FollowList.tsx (1)

35-35: 라우팅 경로 변경이 일관성 있게 적용되었습니다.

/feed/followlist에서 /follow/followlist로의 경로 변경이 전체 라우팅 구조 개선과 일치합니다.

src/components/feed/MyFollower.tsx (1)

73-73: 라우팅 경로 변경이 올바르게 적용되었습니다.

/feed/followerlist에서 /follow/followerlist로의 변경이 전체 라우팅 구조와 일치합니다.

src/components/search/SearchBar.tsx (2)

56-56: 검색바 패딩 조정으로 더 컴팩트한 UI 구현.

패딩을 16px 20px에서 8px 12px로 줄여 새로운 사용자 검색 기능에 적합한 더 컴팩트한 디자인을 구현했습니다.


78-78: 텍스트 커서 색상 개선으로 사용자 경험 향상.

네온 그린 색상의 커서로 입력 시각적 피드백을 개선했습니다.

src/App.tsx (1)

4-4: 글로벌 모달 시스템 통합이 올바르게 구현되었습니다.

ModalContainer를 앱 루트에 추가하여 전역 모달 관리를 가능하게 했습니다. 이는 모달이 최상위 레이어에서 렌더링되도록 하는 올바른 아키텍처 접근법입니다.

Also applies to: 11-11

src/pages/index.tsx (1)

23-23: 사용자 검색 라우트가 올바르게 추가되었습니다.

UserSearch 컴포넌트를 위한 새로운 라우트 /feed/search가 기존 라우팅 패턴과 일관성 있게 추가되었습니다.

Also applies to: 39-39

src/pages/feed/FollowerListPage.tsx (2)

6-7: 모듈 구조 개선이 잘 되었습니다

외부 모듈에서 목업 데이터와 타입을 가져오는 방식으로 리팩토링하여 코드의 재사용성과 유지보수성이 향상되었습니다.


11-11: 타입 안전성 향상

useParams 훅에 명시적으로 UserProfileType 제네릭 타입을 지정하여 타입 안전성이 개선되었습니다.

src/pages/feed/Feed.tsx (3)

10-10: 네비게이션 훅 추가 승인

검색 기능을 위한 useNavigate 훅 추가가 적절합니다.


24-26: 검색 네비게이션 로직 구현 완료

검색 페이지로의 네비게이션 로직이 간단명료하게 구현되었습니다.


34-34: MainHeader 컴포넌트 연동 완료

leftButtonClick prop을 통해 검색 버튼 클릭 이벤트가 적절히 연결되었습니다.

src/data/userData.ts (2)

1-2: 타입 import 적절함

UserProfileItemProps 타입을 올바르게 import하여 타입 안전성을 보장했습니다.


3-38: 목업 데이터 구조 검증 완료: 이상 없음

  • userId 중복 없음
  • 필수 필드(profileImgUrl, userName, userTitle, titleColor, userId) 모두 포함
  • isFollowed는 선택 속성이므로 일부 항목에서 누락된 것이 의도된 동작입니다.
src/hooks/useModal.ts (1)

1-2: 의존성 import 적절함

모달 스토어와 타입들을 올바르게 import했습니다.

src/pages/feed/FeedDetailPage.tsx (1)

11-11: 모달 스토어 import 적절함

모달 기능을 위한 useModalStore import가 올바르게 추가되었습니다.

src/components/common/Modal/ConfirmModal.tsx (1)

53-65: 버튼 스타일링 잘 구현됨

variant에 따른 조건부 스타일링이 깔끔하게 구현되어 있고, 글로벌 디자인 시스템과 일관성을 유지하고 있습니다. 커서 포인터 설정도 적절합니다.

src/types/user.ts (1)

1-13: 타입 정의 잘 구조화됨

사용자 프로필 관련 타입들이 잘 정의되어 있고, 필수/선택 필드가 적절히 구분되어 있습니다. 별도 파일로 분리하여 재사용성도 좋습니다.

src/components/feed/UserProfileItem.tsx (3)

5-5: 타입 중앙화 좋은 개선사항

UserProfileItemProps 타입을 중앙의 src/types/user.ts에서 임포트하는 것은 코드 일관성과 유지보수성을 크게 향상시킵니다.


16-16: prop 기반 조건부 렌더링 개선

useLocation에서 type prop으로 변경한 것은 컴포넌트를 더 유연하고 테스트하기 쉽게 만든 훌륭한 리팩토링입니다. URL 경로에 의존하지 않아 결합도가 낮아졌습니다.

Also applies to: 48-58


22-22: 라우팅 경로 otherfeed/:userId가 올바르게 정의되었습니다

  • src/pages/index.tsx
    <Route path="otherfeed/:userId" element={<OtherFeedPage />} />
  • UserProfileItem, FollowList, PostHeader 컴포넌트에서 모두 navigate(/otherfeed/${userId}) 호출이 정상 동작합니다.

따라서 변경된 네비게이션 경로는 라우팅 구조와 일치합니다.

src/pages/feed/UserSearch.tsx (2)

33-41: 검색 로직 잘 구현됨

검색어 중복 제거와 최대 5개 제한, 최근 순 정렬 로직이 깔끔하게 구현되어 있습니다. 사용자 경험을 고려한 좋은 구현입니다.


57-62: useEffect를 통한 상태 동기화 적절

검색어가 비워졌을 때 검색 상태를 초기화하는 로직이 적절하게 구현되어 있습니다.

src/stores/useModalStore.ts (1)

3-3: 전체적인 모달 스토어 구조가 잘 설계되었습니다

Zustand를 사용한 글로벌 모달 상태 관리 구조가 깔끔하고 확장 가능하게 잘 구현되었습니다. 타입 안전성과 관심사 분리가 잘 되어 있습니다.

Also applies to: 13-17, 27-33

src/components/common/Modal/MoreMenu.tsx (1)

48-67: Button 컴포넌트 스타일링이 잘 구현되었습니다

variant 기반의 조건부 스타일링과 first/last-of-type 선택자를 사용한 패딩 처리가 깔끔하게 구현되었습니다.

src/components/common/Modal/ModalContainer.tsx (3)

11-16: body overflow 제어가 잘 구현되었습니다

모달이 열렸을 때 배경 스크롤을 방지하는 로직과 cleanup 함수가 적절하게 구현되었습니다.


33-50: 모달 오버레이 스타일링이 우수합니다

고정 위치, 블러 효과, 적절한 z-index 설정으로 전문적인 모달 오버레이가 잘 구현되었습니다. 반응형 너비 제한도 적절합니다.


8-32: 전체적인 모달 컨테이너 아키텍처가 훌륭합니다

글로벌 모달 관리를 위한 중앙집중식 접근 방식이 잘 구현되었습니다. 모달 타입별 조건부 렌더링과 상태 관리가 깔끔하게 분리되어 있습니다.

Comment on lines +7 to +19
<Container>
<div className="title">{title}</div>
<div className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose}>
아니요
</Button>
<Button variant="yes" onClick={onConfirm}>
</Button>
</ButtonContainer>
</Container>
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선 필요

모달 컴포넌트에 접근성 기능이 누락되어 있습니다. 스크린 리더 지원과 키보드 네비게이션을 위한 속성들을 추가해주세요.

-    <Container>
+    <Container role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
-      <div className="title">{title}</div>
-      <div className="disc">{disc}</div>
+      <div id="modal-title" className="title">{title}</div>
+      <div id="modal-description" className="disc">{disc}</div>
       <ButtonContainer>
-        <Button variant="no" onClick={onClose}>
+        <Button variant="no" onClick={onClose} type="button">
           아니요
         </Button>
-        <Button variant="yes" onClick={onConfirm}>
+        <Button variant="yes" onClick={onConfirm} type="button" autoFocus>
           예
         </Button>
       </ButtonContainer>
     </Container>
📝 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
<Container>
<div className="title">{title}</div>
<div className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose}>
아니요
</Button>
<Button variant="yes" onClick={onConfirm}>
</Button>
</ButtonContainer>
</Container>
);
<Container role="dialog" aria-labelledby="modal-title" aria-describedby="modal-description">
<div id="modal-title" className="title">{title}</div>
<div id="modal-description" className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose} type="button">
아니요
</Button>
<Button variant="yes" onClick={onConfirm} type="button" autoFocus>
</Button>
</ButtonContainer>
</Container>
);
🤖 Prompt for AI Agents
In src/components/common/Modal/ConfirmModal.tsx between lines 7 and 19, the
modal component lacks accessibility features. Add appropriate ARIA roles such as
role="dialog" and aria-modal="true" to the container element, ensure the title
has an accessible label using aria-labelledby or aria-label, and enable keyboard
navigation by managing focus trap within the modal and allowing closing with the
Escape key. These changes will improve screen reader support and keyboard
usability.

Comment on lines 22 to 25
case 'confirm':
return <ConfirmModal {...(modalProps as ConfirmModalProps)} onClose={closeModal} />;
case 'moremenu':
return <MoreMenu {...(modalProps as MoreMenuProps)} onClose={closeModal} />;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 개선: 타입 가드 사용 권장

타입 단언(as) 대신 타입 가드를 사용하면 더 안전한 타입 체크가 가능합니다.

const renderModal = () => {
  switch (modalType) {
    case 'confirm':
      if (modalProps && 'title' in modalProps) {
        return <ConfirmModal {...modalProps} onClose={closeModal} />;
      }
      return null;
    case 'moremenu':
      if (modalProps && ('onEdit' in modalProps || 'onDelete' in modalProps)) {
        return <MoreMenu {...modalProps} onClose={closeModal} />;
      }
      return null;
    default:
      return null;
  }
};
🤖 Prompt for AI Agents
In src/components/common/Modal/ModalContainer.tsx lines 22 to 25, replace the
current usage of type assertions (as) with explicit type guards to improve type
safety. Implement conditional checks on modalProps properties specific to each
modal type before rendering, returning null if the checks fail. This avoids
unsafe casting and ensures the component only renders when modalProps matches
the expected shape.

Comment on lines +24 to +29
<UserProfileItem
key={index}
{...user}
type={type as UserProfileType}
isLast={index === mockUserList.length - 1}
/>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

타입 캐스팅 검증 필요

type as UserProfileType 타입 캐스팅이 안전한지 확인이 필요합니다. typeundefined일 가능성이 있습니다.

다음 스크립트로 타입 안전성을 검증해보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: UserProfileType과 관련된 라우트 설정을 확인하여 type 파라미터가 항상 유효한 값인지 검증

# 라우트 설정에서 type 파라미터 사용을 확인
rg -A 5 -B 5 "followerlist|followlist" --type tsx --type ts

# UserProfileItem 컴포넌트에서 type prop 사용 방식 확인
ast-grep --pattern 'interface UserProfileItemProps {
  $$$
  type?: $_
  $$$
}'

Length of output: 639


다음 스크립트로 src/pages/feed/FollowerListPage.tsx에서 type이 어디서 유래하는지 확인하겠습니다:


🏁 Script executed:

#!/bin/bash
# FollowerListPage.tsx 파일 위치 확인 및 상단 200줄 출력
FILE=$(find . -type f -name "FollowerListPage.tsx")
echo "Found file: $FILE"
sed -n '1,200p' "$FILE"

Length of output: 2187


타입 가드 및 null 체크 추가: useParams로 받은 type 검증 필요

useParams<{ type: UserProfileType }>()로 받아오는 type은 런타임에서 string | undefined이므로 바로 type as UserProfileType로 캐스팅하는 것은 안전하지 않습니다. 아래와 같이 null 체크 및 타입 가드를 추가해주세요.

수정 위치:

  • src/pages/feed/FollowerListPage.tsx

예시:

- const { type } = useParams<{ type: UserProfileType }>();
+ const params = useParams<{ type: UserProfileType }>();
+ if (!params.type) {
+   // 타입 누락 시 처리 (리다이렉트, 에러 화면 등)
+   navigate(-1);
+   return null;
+ }
+ const type = params.type;

-       <UserProfileItem
-         key={index}
-         {...user}
-         type={type as UserProfileType}
-         isLast={index === mockUserList.length - 1}
-       />
+       <UserProfileItem
+         key={index}
+         {...user}
+         type={type}
+         isLast={index === mockUserList.length - 1}
+       />

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

🤖 Prompt for AI Agents
In src/pages/feed/FollowerListPage.tsx around lines 24 to 29, the 'type'
parameter obtained from useParams is cast directly to UserProfileType without
runtime validation, which is unsafe. Add a null check and implement a type guard
function to verify that 'type' is a valid UserProfileType before using it. Only
pass 'type' to UserProfileItem if it passes the type guard; otherwise, handle
the undefined or invalid case appropriately to prevent runtime errors.

Comment on lines +83 to +95
(
{isSearched ? (
<UserSearchResult
type={'searched'}
searchedUserList={mockUserList}
></UserSearchResult>
) : (
<UserSearchResult
type={'searching'}
searchedUserList={mockUserList}
></UserSearchResult>
)}
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

디버깅용 괄호 제거 필요

JSX 주변의 불필요한 괄호들이 남아있습니다. 이는 디버깅 과정에서 남은 것으로 보입니다.

         {isSearching ? (
-          <>
-            (
             {isSearched ? (
               <UserSearchResult
                 type={'searched'}
                 searchedUserList={mockUserList}
               ></UserSearchResult>
             ) : (
               <UserSearchResult
                 type={'searching'}
                 searchedUserList={mockUserList}
               ></UserSearchResult>
             )}
-            )
-          </>
         ) : (

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

🤖 Prompt for AI Agents
In src/pages/feed/UserSearch.tsx between lines 83 and 95, remove the unnecessary
parentheses surrounding the JSX elements that were likely left from debugging.
These extra parentheses are not needed and should be deleted to clean up the
code.

<UserProfileItem
key={user.userId}
{...user}
type="followerlist"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

의미적으로 부정확한 type 값

검색 결과에서 UserProfileItemtype="followerlist"를 전달하고 있는데, 검색 컨텍스트에서는 의미적으로 맞지 않습니다. 검색 전용 타입을 추가하거나 다른 값을 사용하는 것을 고려해보세요.

src/types/user.ts에 검색용 타입을 추가:

-export type UserProfileType = 'followlist' | 'followerlist';
+export type UserProfileType = 'followlist' | 'followerlist' | 'searchresult';

그리고 여기서 사용:

-              type="followerlist"
+              type="searchresult"
🤖 Prompt for AI Agents
In src/pages/feed/UserSearchResult.tsx at line 29, the prop type passed to
UserProfileItem is incorrectly set as "followerlist", which is semantically
inaccurate for the search context. To fix this, add a new type value specific
for search results in src/types/user.ts, then update the type prop here to use
that new search-specific type instead of "followerlist".


export const useModalStore = create<ModalState>(set => ({
modalType: null,
modalProps: {},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 개선: 초기값을 undefined로 변경

초기 modalProps 값을 빈 객체 {}로 설정하면 타입 체크 시 문제가 발생할 수 있습니다. undefined로 설정하는 것이 더 안전합니다.

-  modalProps: {},
+  modalProps: undefined,
📝 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
modalProps: {},
modalProps: undefined,
🤖 Prompt for AI Agents
In src/stores/useModalStore.ts at line 29, change the initial value of
modalProps from an empty object {} to undefined to improve type safety and avoid
potential type checking issues.

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

🧹 Nitpick comments (12)
src/pages/feed/FeedDetailPage.tsx (1)

22-36: 모달 구현이 잘되어 있지만 useModal 훅 사용을 고려해보세요.

모달 중첩 로직이 잘 구현되어 있습니다. 하지만 새로 만든 useModal 훅을 사용하면 더 깔끔한 코드가 될 수 있습니다:

-  const { openModal } = useModalStore();
+  const { openMoreMenu, openConfirm } = useModal();

   const handleMoreClick = () => {
-    openModal('moremenu', {
+    openMoreMenu({
       onEdit: () => {
         console.log('수정하기 클릭');
       },
       onDelete: () => {
         console.log('삭제하기 클릭');
-        openModal('confirm', {
+        openConfirm({
           title: '이 피드를 삭제하시겠어요?',
           disc: '삭제 후에는 되돌릴 수 없어요',
           onConfirm: () => console.log('확인 클릭'),
         });
       },
     });
   };

이렇게 하면 타입 안전성이 더 높아지고 코드가 더 읽기 쉬워집니다.

src/pages/feed/UserSearchResult.tsx (1)

12-15: 불필요한 함수 복잡성을 제거하세요.

isEmptySearchedUserList 함수가 단순한 길이 체크를 불필요하게 복잡하게 만들고 있습니다.

다음과 같이 간소화할 수 있습니다:

-  const isEmptySearchedUserList = () => {
-    if (searchedUserList.length === 0) return true;
-    else return false;
-  };

그리고 JSX에서 직접 사용:

-        {isEmptySearchedUserList() ? (
+        {searchedUserList.length === 0 ? (
src/components/common/Modal/ConfirmModal.tsx (1)

3-3: Props 인터페이스의 명명 규칙을 개선하세요.

disc 프로퍼티 명이 축약형으로 되어 있어 가독성이 떨어집니다.

더 명확한 이름을 사용하는 것을 고려해보세요. 하지만 이미 useModalStore에서 정의되었다면 해당 파일에서도 함께 수정해야 합니다:

// useModalStore.ts에서
interface ConfirmModalProps {
  title: string;
  description: string; // disc -> description
  onConfirm?: () => void;
  onClose?: () => void;
}
src/types/user.ts (2)

1-1: 타입 이름의 일관성을 확인하세요.

UserProfileType에서 'followlist''followerlist'의 네이밍이 일관성이 없습니다.

더 일관된 네이밍을 고려해보세요:

-export type UserProfileType = 'followlist' | 'followerlist';
+export type UserProfileType = 'following' | 'followers';

또는:

-export type UserProfileType = 'followlist' | 'followerlist';
+export type UserProfileType = 'follow-list' | 'follower-list';

3-13: 타입 정의에 JSDoc 문서를 추가하세요.

인터페이스와 각 프로퍼티에 대한 설명이 없어서 다른 개발자들이 사용하기 어려울 수 있습니다.

JSDoc 주석을 추가하여 문서화하세요:

+/**
+ * 사용자 프로필 아이템의 props 인터페이스
+ */
 export interface UserProfileItemProps {
+  /** 프로필 이미지 URL */
   profileImgUrl: string;
+  /** 사용자명 */
   userName: string;
+  /** 사용자 타이틀/직함 */
   userTitle: string;
+  /** 타이틀 색상 */
   titleColor: string;
+  /** 팔로워 수 (선택사항) */
   followerCount?: number;
+  /** 팔로우 여부 (선택사항) */
   isFollowed?: boolean;
+  /** 사용자 ID */
   userId: number;
+  /** 리스트의 마지막 아이템 여부 (선택사항) */
   isLast?: boolean;
+  /** 프로필 타입 (선택사항) */
   type?: UserProfileType;
 }
src/components/common/Modal/MoreMenu.tsx (1)

38-45: 반응형 디자인을 개선하세요.

현재 min-width: 320pxmax-width: 767px로 제한되어 있는데, 더 큰 화면에서도 적절히 동작하도록 개선할 수 있습니다.

더 유연한 반응형 디자인을 고려해보세요:

   min-width: 320px;
-  max-width: 767px;
+  max-width: min(100vw, 480px);
   width: 100%;
+  margin: 0 auto;
src/pages/feed/UserSearch.tsx (3)

83-83: 불필요한 괄호를 제거하세요.

JSX 렌더링에서 불필요한 괄호가 있습니다.

-            (
             {isSearched ? (
               <UserSearchResult
                 type={'searched'}
                 searchedUserList={mockUserList}
               ></UserSearchResult>
             ) : (
               <UserSearchResult
                 type={'searching'}
                 searchedUserList={mockUserList}
               ></UserSearchResult>
             )}
-            )

Also applies to: 95-95


8-8: import 경로 일관성을 개선하세요.

다른 import들은 절대 경로(@/)를 사용하는데, 이 라인만 상대 경로를 사용하고 있습니다.

-import leftArrow from '../../assets/common/leftArrow.svg';
+import leftArrow from '@/assets/common/leftArrow.svg';

19-25: 하드코딩된 데이터를 상수로 분리하는 것을 고려하세요.

초기 최근 검색어가 하드코딩되어 있습니다. 추후 확장성을 위해 별도 상수 파일로 분리하거나 로컬 스토리지에서 불러오는 것을 고려해보세요.

+const INITIAL_RECENT_SEARCHES = ['닉네임', '작가', '하위', 'Thip', '책벌레'];
+
 const [recentSearches, setRecentSearches] = useState<string[]>([
-    '닉네임',
-    '작가',
-    '하위',
-    'Thip',
-    '책벌레',
+    INITIAL_RECENT_SEARCHES
   ]);
src/components/common/Modal/ModalContainer.tsx (1)

20-29: 타입 안전성을 개선할 수 있습니다.

현재 타입 캐스팅(as)을 사용하고 있는데, 더 안전한 타입 가드를 사용하는 것을 고려해보세요.

const renderModal = () => {
  switch (modalType) {
    case 'confirm':
-     return <ConfirmModal {...(modalProps as ConfirmModalProps)} onClose={closeModal} />;
+     return <ConfirmModal {...(modalProps || {})} onClose={closeModal} />;
    case 'moremenu':
-     return <MoreMenu {...(modalProps as MoreMenuProps)} onClose={closeModal} />;
+     return <MoreMenu {...(modalProps || {})} onClose={closeModal} />;
    default:
      return null;
  }
};

또는 더 엄격한 타입 검증을 원한다면:

const isConfirmModalProps = (props: any): props is ConfirmModalProps => {
  return props && typeof props.title === 'string' && typeof props.disc === 'string';
};
src/stores/useModalStore.ts (2)

8-8: 필드명을 명확하게 개선하는 것을 고려하세요.

disc 필드가 description의 축약형인 것으로 보입니다. 가독성을 위해 전체 이름을 사용하는 것이 좋겠습니다.

export interface ConfirmModalProps {
  title: string;
-  disc: string;
+  description: string;
  onConfirm?: () => void;
  onClose?: () => void;
}

이 변경을 적용한다면 해당 interface를 사용하는 다른 컴포넌트들도 함께 업데이트해야 합니다.


29-29: 기본값 설정을 개선할 수 있습니다.

modalProps의 기본값으로 빈 객체를 사용하는 것보다 undefined를 사용하는 것이 더 명확할 수 있습니다.

export const useModalStore = create<ModalState>(set => ({
  modalType: null,
-  modalProps: {},
+  modalProps: undefined,
  isModalOpen: false,
  openModal: (type, props) => set({ modalType: type, modalProps: props, isModalOpen: true }),
-  closeModal: () => set({ modalType: null, modalProps: {}, isModalOpen: false }),
+  closeModal: () => set({ modalType: null, modalProps: undefined, isModalOpen: false }),
}));
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ec2d34 and 6168da1.

📒 Files selected for processing (18)
  • src/App.tsx (1 hunks)
  • src/components/common/Modal/ConfirmModal.tsx (1 hunks)
  • src/components/common/Modal/ModalContainer.tsx (1 hunks)
  • src/components/common/Modal/MoreMenu.tsx (1 hunks)
  • src/components/feed/FollowList.tsx (1 hunks)
  • src/components/feed/MyFollower.tsx (1 hunks)
  • src/components/feed/UserProfileItem.tsx (3 hunks)
  • src/components/search/SearchBar.tsx (2 hunks)
  • src/data/userData.ts (1 hunks)
  • src/hooks/useModal.ts (1 hunks)
  • src/pages/feed/Feed.tsx (2 hunks)
  • src/pages/feed/FeedDetailPage.tsx (1 hunks)
  • src/pages/feed/FollowerListPage.tsx (2 hunks)
  • src/pages/feed/UserSearch.tsx (1 hunks)
  • src/pages/feed/UserSearchResult.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/stores/useModalStore.ts (1 hunks)
  • src/types/user.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/hooks/useModal.ts (1)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/pages/feed/FollowerListPage.tsx (2)
src/types/user.ts (1)
  • UserProfileType (1-1)
src/data/userData.ts (1)
  • mockUserList (3-38)
src/pages/feed/FeedDetailPage.tsx (1)
src/stores/useModalStore.ts (1)
  • useModalStore (27-33)
src/data/userData.ts (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/components/common/Modal/MoreMenu.tsx (2)
src/stores/useModalStore.ts (1)
  • MoreMenuProps (13-17)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/components/feed/UserProfileItem.tsx (1)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/components/common/Modal/ConfirmModal.tsx (2)
src/stores/useModalStore.ts (1)
  • ConfirmModalProps (6-11)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/components/common/Modal/ModalContainer.tsx (2)
src/stores/useModalStore.ts (3)
  • useModalStore (27-33)
  • ConfirmModalProps (6-11)
  • MoreMenuProps (13-17)
src/components/common/Wrapper.tsx (1)
  • Wrapper (3-14)
src/pages/feed/UserSearchResult.tsx (2)
src/types/user.ts (1)
  • UserProfileItemProps (3-13)
src/styles/global/global.ts (2)
  • typography (56-76)
  • colors (4-53)
🔇 Additional comments (32)
src/components/search/SearchBar.tsx (2)

56-56: 패딩 조정이 적절합니다.

검색바의 패딩을 16px 20px에서 8px 12px로 줄여 더 컴팩트한 UI를 만들었습니다. 사용자 검색 페이지의 고정된 검색바에 적합한 조정입니다.


78-78: 캐럿 색상 설정이 일관성 있게 적용되었습니다.

입력 필드의 캐럿 색상을 네온 그린으로 설정하여 앱의 전체적인 컬러 테마와 일관성을 유지했습니다.

src/components/feed/FollowList.tsx (1)

35-35: 라우팅 경로 업데이트가 일관성 있게 적용되었습니다.

팔로우 리스트 경로를 /feed/followlist에서 /follow/followlist로 변경하여 라우팅 구조를 개선했습니다. MyFollower 컴포넌트의 변경과 일치하는 일관된 접근 방식입니다.

src/App.tsx (2)

4-4: 모달 시스템 통합을 위한 import가 추가되었습니다.

전역 모달 관리를 위한 ModalContainer 컴포넌트를 import했습니다.


11-11: 전역 모달 시스템이 올바르게 통합되었습니다.

ModalContainer를 App 컴포넌트에 추가하여 앱 전체에서 모달을 중앙 관리할 수 있게 되었습니다. 라우터 다음에 배치한 것이 적절하며, 모달이 모든 콘텐츠 위에 렌더링될 수 있습니다.

src/components/feed/MyFollower.tsx (1)

73-73: 팔로워 리스트 라우팅 경로가 일관성 있게 업데이트되었습니다.

팔로워 리스트 경로를 /feed/followerlist에서 /follow/followerlist로 변경하여 FollowList 컴포넌트와 일치하는 라우팅 구조 개선이 이루어졌습니다.

src/pages/index.tsx (2)

23-23: 사용자 검색 컴포넌트 import가 추가되었습니다.

새로운 사용자 검색 기능을 위한 UserSearch 컴포넌트를 import했습니다.


39-39: 사용자 검색 라우트가 적절히 추가되었습니다.

feed/search 경로에 UserSearch 컴포넌트를 연결하여 새로운 사용자 검색 기능을 라우팅 시스템에 통합했습니다. 기존 라우트 명명 규칙과 일치하며 논리적인 경로 구조입니다.

src/pages/feed/FollowerListPage.tsx (2)

6-7: 데이터 중앙화와 타입 정의가 잘 구현되었습니다.

mockUserList를 외부 모듈로 분리하고 UserProfileType을 별도 타입으로 정의한 것은 코드 재사용성과 유지보수성을 높이는 좋은 접근입니다.


11-11: useParams의 타입 안전성이 개선되었습니다.

제네릭을 사용하여 useParams의 반환 타입을 명시적으로 지정한 것이 좋습니다. 이는 컴파일 타임에 타입 오류를 잡을 수 있게 해줍니다.

src/pages/feed/Feed.tsx (3)

10-10: 네비게이션 훅 추가가 적절합니다.

React Router의 useNavigate 훅을 추가하여 프로그래매틱 네비게이션을 구현하는 것이 적절합니다.


24-26: 검색 페이지로의 네비게이션이 잘 구현되었습니다.

handleSearchButton 함수가 명확하고 간결하게 구현되었으며, 라우팅 경로도 일관성 있게 정의되었습니다.


34-34: MainHeader와의 통합이 적절합니다.

leftButtonClick 프롭으로 검색 핸들러를 전달하는 방식이 컴포넌트 간의 책임 분리를 잘 유지하고 있습니다.

src/hooks/useModal.ts (1)

1-20: 모달 관리를 위한 훌륭한 추상화입니다.

이 커스텀 훅은 모달 스토어를 깔끔하게 추상화하여 다음과 같은 이점을 제공합니다:

  • 타입 안전성: 각 모달 타입에 대한 명시적인 프롭 타이핑
  • 사용 편의성: 직관적인 함수명 (openConfirm, openMoreMenu)
  • 일관성: 모든 컴포넌트에서 동일한 방식으로 모달 사용 가능

PR 목표에서 언급된 "불필요한 복잡성" 우려와는 달리, 이 훅은 타입 안전성과 개발자 경험을 크게 개선하는 가치 있는 추상화입니다.

src/data/userData.ts (1)

1-38: 사용자 데이터 중앙화가 잘 구현되었습니다.

목업 데이터를 별도 모듈로 분리한 것이 좋은 접근입니다:

  • 타입 안전성: UserProfileItemProps 인터페이스를 사용한 정확한 타이핑
  • 재사용성: 여러 컴포넌트에서 동일한 데이터 사용 가능
  • 유지보수성: 데이터 변경 시 한 곳에서만 수정
  • 테스트 데이터: 다양한 사용자 상태(팔로우/언팔로우)를 포함한 현실적인 데이터

데이터 구조가 인터페이스와 일치하며, 선택적 속성들도 적절히 활용되었습니다.

src/pages/feed/FeedDetailPage.tsx (2)

11-11: 모달 스토어 import가 적절합니다.

useModalStore를 직접 import하여 모달 기능을 구현하고 있습니다.


25-25: 실제 구현으로 교체해야 할 플레이스홀더 코드입니다.

현재 모든 액션이 console.log로 구현되어 있습니다. 실제 편집/삭제 로직으로 교체가 필요합니다:

  • 수정하기: 편집 페이지로 이동 또는 편집 모드 활성화
  • 삭제하기: 실제 삭제 API 호출
  • 확인: 삭제 실행 및 페이지 이동
onEdit: () => {
  navigate(`/feed/edit/${feedId}`);
},
onDelete: () => {
  // 삭제 확인 모달 표시
},
onConfirm: async () => {
  try {
    await deleteFeed(feedId);
    navigate('/feed');
  } catch (error) {
    // 에러 처리
  }
},

Also applies to: 28-28, 32-32

src/pages/feed/UserSearchResult.tsx (2)

39-65: 스타일 구현이 잘 구조화되어 있습니다.

styled-components를 사용한 스타일링이 깔끔하고 일관성 있게 구현되었으며, 디자인 시스템의 색상과 타이포그래피를 올바르게 사용하고 있습니다.


29-29: UserSearchResult에서 UserProfileItem의 type 값 확인 필요

UserProfileItemPropstypeUserProfileType = 'followlist' | 'followerlist'로 정의되어 있으며, 하드코딩된 "followerlist" 자체는 유효한 값입니다.
다만, 검색 결과 화면(src/pages/feed/UserSearchResult.tsx)에서 모든 검색 결과를 ‘팔로워 목록’ 컨텍스트로 렌더링하도록 설정된 것이 의도된 동작인지 재확인해 주세요.

  • 파일: src/pages/feed/UserSearchResult.tsx
  • 라인: 29 (<UserProfileItem … type="followerlist" …/>)

의도된 동작이 맞다면 이대로 유지해도 좋지만, 앞으로 유지보수 편의성을 위해 해당 문자열을 상수로 관리하거나, 주석을 달아 의도를 명시해 두는 것을 권장드립니다.

src/components/common/Modal/ConfirmModal.tsx (1)

22-45: 모달 컨테이너 스타일링이 잘 구현되어 있습니다.

고정 크기와 적절한 패딩, 그리고 디자인 시스템과 일관성 있는 색상 사용이 잘 되어 있습니다. 타이포그래피 시스템도 올바르게 적용되었습니다.

src/types/user.ts (1)

10-10: userId 타입 일관성 확인됨

프로젝트 전반에서 userId가 모두 number로 선언·사용되고 있어, 별도 변경 없이 현재 설계를 유지하셔도 됩니다.

필요 시 string/UUID로 전환하려면 다음 위치를 포함해 관련 타입 선언 및 사용부 전반을 리팩토링해야 합니다:

  • src/types/user.ts (line 10)
  • src/types/post.ts (lines 4, 41, 54)
  • src/data/userData.ts, src/data/postData.ts
  • src/components/feed/FollowList.tsx
  • src/components/common/Post/PostHeader.tsx
src/components/feed/UserProfileItem.tsx (4)

16-16: 기본값이 적절하게 설정되었습니다.

type prop에 'followlist' 기본값을 설정한 것이 좋습니다. 이는 하위 호환성을 보장하면서도 명시적인 타입 지정을 가능하게 합니다.


53-58: 팔로워리스트 버튼에서 클릭 핸들러가 제거되었습니다.

이전에 있던 handleFollowerListClick 함수가 제거되어 팔로워리스트 버튼이 클릭할 수 없는 상태가 되었습니다.

이것이 의도된 변경인지, 아니면 기능이 누락된 것인지 확인이 필요합니다. 팔로워 목록으로의 네비게이션 기능이 필요하다면 복원해야 합니다:

const handleFollowerListClick = (e: React.MouseEvent) => {
  e.stopPropagation();
  navigate(`/follow/follower-list/${userId}`);
};

그리고 버튼에 클릭 핸들러 추가:

-          <div className="followlistbutton">
+          <div className="followlistbutton" onClick={handleFollowerListClick}>

48-52: 조건부 렌더링 로직이 개선되었습니다.

URL 기반 조건부 렌더링에서 prop 기반 조건부 렌더링으로 변경한 것이 더 명확하고 예측 가능한 코드를 만듭니다. 컴포넌트의 책임이 더 명확해졌습니다.


22-22: 라우팅 경로 정의 확인 완료

src/pages/index.tsx에 다음과 같이 /otherfeed/:userId 경로가 정의되어 있어 변경이 올바릅니다:

<Route path="otherfeed/:userId" element={<OtherFeedPage />} />

다른 컴포넌트(UserProfileItem, FollowList, PostHeader)에서도 동일한 경로를 사용 중이므로 일관성이 유지됩니다.

src/components/common/Modal/MoreMenu.tsx (2)

7-8: 이벤트 처리 로직이 올바르게 구현되었습니다.

오버레이 클릭 시 모달 닫기와 내부 컨테이너에서 이벤트 버블링 방지가 적절하게 구현되어 있습니다.


30-46: 모바일 퍼스트 디자인이 잘 적용되었습니다.

하단 고정 포지셔닝과 적절한 z-index 설정, 그리고 둥근 모서리 디자인이 모바일 환경에 최적화되어 있습니다.

src/pages/feed/UserSearch.tsx (2)

37-40: 검색어 중복 제거 로직이 잘 구현되었습니다.

동일한 검색어의 중복을 방지하고 최대 5개까지만 유지하는 로직이 효율적으로 구현되어 있습니다.


57-62: useEffect를 통한 상태 동기화가 적절합니다.

검색어가 비어있을 때 검색 상태를 초기화하는 로직이 올바르게 구현되어 있습니다.

src/components/common/Modal/ModalContainer.tsx (2)

11-16: body scroll 제어 로직이 잘 구현되었습니다.

모달이 열렸을 때 배경 스크롤을 방지하는 로직과 cleanup 함수가 적절히 구현되어 있습니다.


33-50: 모달 오버레이 스타일링이 적절합니다.

fixed positioning, backdrop blur, z-index 설정이 모달 UI/UX에 적합하게 구현되어 있습니다. 반응형 디자인도 고려되어 있습니다.

src/stores/useModalStore.ts (1)

27-33: Zustand 스토어 구현이 잘 되어 있습니다.

모달 상태 관리를 위한 깔끔한 API가 제공되고 있으며, 타입 안전성도 적절히 보장되고 있습니다.

Comment on lines +5 to +20
const ConfirmModal = ({ title, disc, onConfirm, onClose }: ConfirmModalProps) => {
return (
<Container>
<div className="title">{title}</div>
<div className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose}>
아니요
</Button>
<Button variant="yes" onClick={onConfirm}>
</Button>
</ButtonContainer>
</Container>
);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성과 키보드 네비게이션을 개선하세요.

모달 컴포넌트에 접근성 속성과 키보드 이벤트 처리가 누락되어 있습니다.

다음과 같이 접근성을 개선할 수 있습니다:

-const ConfirmModal = ({ title, disc, onConfirm, onClose }: ConfirmModalProps) => {
+const ConfirmModal = ({ title, disc, onConfirm, onClose }: ConfirmModalProps) => {
+  const handleKeyDown = (e: React.KeyboardEvent) => {
+    if (e.key === 'Escape') onClose?.();
+    if (e.key === 'Enter') onConfirm?.();
+  };
+
   return (
-    <Container>
+    <Container 
+      role="dialog" 
+      aria-labelledby="modal-title" 
+      aria-describedby="modal-description"
+      onKeyDown={handleKeyDown}
+      tabIndex={-1}
+    >
-      <div className="title">{title}</div>
-      <div className="disc">{disc}</div>
+      <div id="modal-title" className="title">{title}</div>
+      <div id="modal-description" className="disc">{disc}</div>
📝 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 ConfirmModal = ({ title, disc, onConfirm, onClose }: ConfirmModalProps) => {
return (
<Container>
<div className="title">{title}</div>
<div className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose}>
아니요
</Button>
<Button variant="yes" onClick={onConfirm}>
</Button>
</ButtonContainer>
</Container>
);
};
const ConfirmModal = ({ title, disc, onConfirm, onClose }: ConfirmModalProps) => {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') onClose?.();
if (e.key === 'Enter') onConfirm?.();
};
return (
<Container
role="dialog"
aria-labelledby="modal-title"
aria-describedby="modal-description"
onKeyDown={handleKeyDown}
tabIndex={-1}
>
<div id="modal-title" className="title">{title}</div>
<div id="modal-description" className="disc">{disc}</div>
<ButtonContainer>
<Button variant="no" onClick={onClose}>
아니요
</Button>
<Button variant="yes" onClick={onConfirm}>
</Button>
</ButtonContainer>
</Container>
);
};
🤖 Prompt for AI Agents
In src/components/common/Modal/ConfirmModal.tsx around lines 5 to 20, the modal
lacks accessibility attributes and keyboard event handling. Add appropriate ARIA
roles such as role="dialog" and aria-modal="true" to the container element.
Ensure the modal traps keyboard focus when open and supports keyboard
navigation, including handling the Escape key to close the modal. Also, add
meaningful labels and ensure buttons are accessible via keyboard.

Comment on lines +53 to +65
const Button = styled.div<{ variant: 'yes' | 'no' }>`
width: 130px;
height: 44px;
padding: 10px 12px;
border-radius: 12px;
color: ${colors.white};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.semibold};
text-align: center;
line-height: 24px;
background-color: ${({ variant }) => (variant === 'yes' ? colors.purple.main : colors.grey[300])};
cursor: pointer;
`;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Button 컴포넌트의 접근성을 개선하세요.

Button이 div로 구현되어 있어 접근성과 의미론적 마크업이 부족합니다.

실제 button 요소를 사용하도록 개선하세요:

-const Button = styled.div<{ variant: 'yes' | 'no' }>`
+const Button = styled.button<{ variant: 'yes' | 'no' }>`
   width: 130px;
   height: 44px;
   padding: 10px 12px;
   border-radius: 12px;
+  border: none;
   color: ${colors.white};
   font-size: ${typography.fontSize.base};
   font-weight: ${typography.fontWeight.semibold};
   text-align: center;
   line-height: 24px;
   background-color: ${({ variant }) => (variant === 'yes' ? colors.purple.main : colors.grey[300])};
   cursor: pointer;
+
+  &:focus {
+    outline: 2px solid ${colors.purple.sub};
+    outline-offset: 2px;
+  }
 `;
📝 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 Button = styled.div<{ variant: 'yes' | 'no' }>`
width: 130px;
height: 44px;
padding: 10px 12px;
border-radius: 12px;
color: ${colors.white};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.semibold};
text-align: center;
line-height: 24px;
background-color: ${({ variant }) => (variant === 'yes' ? colors.purple.main : colors.grey[300])};
cursor: pointer;
`;
const Button = styled.button<{ variant: 'yes' | 'no' }>`
width: 130px;
height: 44px;
padding: 10px 12px;
border-radius: 12px;
border: none;
color: ${colors.white};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.semibold};
text-align: center;
line-height: 24px;
background-color: ${({ variant }) => (variant === 'yes' ? colors.purple.main : colors.grey[300])};
cursor: pointer;
&:focus {
outline: 2px solid ${colors.purple.sub};
outline-offset: 2px;
}
`;
🤖 Prompt for AI Agents
In src/components/common/Modal/ConfirmModal.tsx between lines 53 and 65, the
Button component is implemented as a styled div, which lacks semantic meaning
and accessibility features. Change the styled component to use a native button
element instead of div, ensuring it retains the same styles and accepts the
variant prop for styling. This will improve accessibility and provide proper
keyboard and screen reader support.

Comment on lines +48 to +67
const Button = styled.div<{ variant: 'edit' | 'delete' }>`
display: flex;
height: 50px;
align-items: center;
color: ${({ variant }) => (variant === 'edit' ? colors.white : colors.red)};
font-size: ${typography.fontSize.base};
font-weight: ${typography.fontWeight.semibold};
line-height: 24px;
border-bottom: 1px solid ${colors.grey[400]};
cursor: pointer;

&:first-of-type {
padding: 8px 12px 16px 12px;
}

&:last-of-type {
padding: 16px 12px 8px 12px;
border-bottom: none;
}
`;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Button 접근성을 개선하세요.

div를 버튼으로 사용하고 있어 접근성과 키보드 네비게이션에 문제가 있습니다.

실제 button 요소나 적절한 ARIA 속성을 사용하세요:

-const Button = styled.div<{ variant: 'edit' | 'delete' }>`
+const Button = styled.button<{ variant: 'edit' | 'delete' }>`
   display: flex;
   height: 50px;
   align-items: center;
+  border: none;
+  background: none;
   color: ${({ variant }) => (variant === 'edit' ? colors.white : colors.red)};
   font-size: ${typography.fontSize.base};
   font-weight: ${typography.fontWeight.semibold};
   line-height: 24px;
   border-bottom: 1px solid ${colors.grey[400]};
   cursor: pointer;
+
+  &:focus {
+    outline: 2px solid ${colors.purple.main};
+    outline-offset: -2px;
+  }
🤖 Prompt for AI Agents
In src/components/common/Modal/MoreMenu.tsx lines 48 to 67, the styled component
Button is currently a div, which causes accessibility and keyboard navigation
issues. Change the Button component to use a native button element instead of a
div, or if you must keep it as a div, add appropriate ARIA roles and keyboard
event handlers to ensure it behaves like a button for screen readers and
keyboard users.

Comment on lines +24 to 30
<UserProfileItem
key={index}
{...user}
type={type as UserProfileType}
isLast={index === mockUserList.length - 1}
/>
))}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 캐스팅을 검증하세요.

type 프롭에 as UserProfileType으로 타입 캐스팅을 사용하고 있는데, useParams에서 반환된 type이 undefined일 가능성이 있습니다. 런타임 오류를 방지하기 위해 타입 가드를 추가하는 것을 고려해보세요.

-            type={type as UserProfileType}
+            type={type || 'followlist'}

또는 컴포넌트 상단에서 타입 검증을 추가할 수 있습니다.

📝 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
<UserProfileItem
key={index}
{...user}
type={type as UserProfileType}
isLast={index === mockUserList.length - 1}
/>
))}
<UserProfileItem
key={index}
{...user}
type={type || 'followlist'}
isLast={index === mockUserList.length - 1}
/>
))}
🤖 Prompt for AI Agents
In src/pages/feed/FollowerListPage.tsx around lines 24 to 30, the type casting
of the 'type' prop to UserProfileType using 'as UserProfileType' is unsafe
because 'type' from useParams may be undefined. To fix this, add a type guard
before rendering UserProfileItem components to ensure 'type' is defined and
valid, or validate and assign a safe default to 'type' at the top of the
component before usage to prevent runtime errors.

{type === 'searching' ? <></> : <ResultHeader>전체 {searchedUserList.length}</ResultHeader>}

{isEmptySearchedUserList() ? (
<EmptyWrapper></EmptyWrapper>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

빈 상태에 대한 사용자 피드백을 제공하세요.

EmptyWrapper가 완전히 비어있어서 사용자에게 검색 결과가 없다는 피드백을 제공하지 않습니다.

사용자 경험을 위해 빈 상태 메시지를 추가하세요:

-          <EmptyWrapper></EmptyWrapper>
+          <EmptyWrapper>
+            <EmptyMessage>검색 결과가 없습니다.</EmptyMessage>
+          </EmptyWrapper>

그리고 스타일 컴포넌트를 추가:

+const EmptyMessage = styled.div`
+  color: ${colors.grey[200]};
+  font-size: ${typography.fontSize.base};
+  font-weight: ${typography.fontWeight.regular};
+  text-align: center;
+`;
🤖 Prompt for AI Agents
In src/pages/feed/UserSearchResult.tsx at line 23, the EmptyWrapper component is
currently empty and does not provide any feedback to the user when there are no
search results. To fix this, add a user-friendly message inside the EmptyWrapper
that clearly indicates no results were found. Additionally, create and apply
appropriate styled components to ensure the message is visually distinct and
consistent with the app's design.

@heeeeyong heeeeyong changed the title feat: 더보기 모달 / 팝업 모달 구현 feat: 더보기 모달, 팝업(Y/N) 모달, 스낵바 구현 Jul 23, 2025
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: 2

🧹 Nitpick comments (4)
src/components/common/Modal/PopupContainer.tsx (1)

27-41: 타입 단언보다 타입 가드 사용을 고려해 보세요.

현재 as 타입 단언을 사용하고 있는데, 타입 가드를 사용하면 더 안전합니다.

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

+const isConfirmModalProps = (props: any): props is ConfirmModalProps =>
+  typeof props?.title === 'string' && typeof props?.disc === 'string';
+
+const isMoreMenuProps = (props: any): props is MoreMenuProps =>
+  props && typeof props === 'object';
+
+const isSnackbarProps = (props: any): props is SnackbarProps =>
+  typeof props?.message === 'string' && typeof props?.variant === 'string';

      case 'confirm-modal': {
+        if (!isConfirmModalProps(popupProps)) return null;
        return (
          <Wrapper>
-            <ConfirmModal {...(popupProps as ConfirmModalProps)} />
+            <ConfirmModal {...popupProps} />
          </Wrapper>
        );
      }
src/components/common/Modal/Snackbar.tsx (2)

9-19: 타이머 로직을 개선할 수 있습니다.

중첩된 setTimeout 사용과 매직 넘버들을 개선하면 더 읽기 쉬운 코드가 될 것입니다.

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

+const SHOW_DURATION = {
+  top: 4000,
+  bottom: 7000,
+} as const;
+
+const HIDE_ANIMATION_DURATION = 2000;

  useEffect(() => {
    setVisible(true);

-    const showDuration = variant === 'bottom' ? 7000 : 4000;
+    const showDuration = SHOW_DURATION[variant];
    const timer = setTimeout(() => {
      setVisible(false);
-      setTimeout(() => onClose(), 2000); // 애니메이션 끝난 후 닫힘
+      setTimeout(() => onClose(), HIDE_ANIMATION_DURATION);
    }, showDuration);

    return () => clearTimeout(timer);
  }, [variant, onClose]);

58-61: 컴포넌트 이름에 오타가 있습니다.

"Discription"은 "Description"의 오타입니다.

-const Discription = styled.div`
+const Description = styled.div`
   font-size: ${typography.fontSize.xs};
   font-weight: ${typography.fontWeight.medium};
 `;

-      <Discription>{message}</Discription>
+      <Description>{message}</Description>
src/stores/usePopupStore.ts (1)

37-43: 초기 상태의 타입 안전성을 개선할 수 있습니다.

popupProps의 초기값으로 빈 객체를 사용하는 것보다 undefined를 사용하는 것이 타입 안전성 측면에서 더 좋습니다.

 export const usePopupStore = create<PopupState>(set => ({
   popupType: null,
-  popupProps: {},
+  popupProps: undefined,
   isOpen: false,
   openPopup: (type, props) => set({ popupType: type, popupProps: props, isOpen: true }),
-  closePopup: () => set({ popupType: null, popupProps: {}, isOpen: false }),
+  closePopup: () => set({ popupType: null, popupProps: undefined, isOpen: false }),
 }));

그리고 PopupState 인터페이스도 수정해야 합니다:

 interface PopupState {
   popupType: PopupType;
-  popupProps?: ConfirmModalProps | MoreMenuProps | SnackbarProps;
+  popupProps: ConfirmModalProps | MoreMenuProps | SnackbarProps | undefined;
   isOpen: boolean;
   openPopup: (type: PopupType, props?: ConfirmModalProps | MoreMenuProps | SnackbarProps) => void;
   closePopup: () => void;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6168da1 and 3bc33a0.

⛔ Files ignored due to path filters (1)
  • src/assets/feed/like.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • src/App.tsx (1 hunks)
  • src/components/common/Modal/MoreMenu.tsx (1 hunks)
  • src/components/common/Modal/PopupContainer.tsx (1 hunks)
  • src/components/common/Modal/Snackbar.tsx (1 hunks)
  • src/components/common/Post/PostBody.tsx (2 hunks)
  • src/components/common/Post/PostFooter.tsx (2 hunks)
  • src/components/feed/TotalFeed.tsx (1 hunks)
  • src/data/postData.ts (3 hunks)
  • src/hooks/usePopupActions.ts (1 hunks)
  • src/pages/feed/FeedDetailPage.tsx (1 hunks)
  • src/stores/usePopupStore.ts (1 hunks)
  • src/types/post.ts (2 hunks)
🧬 Code Graph Analysis (3)
src/components/common/Modal/PopupContainer.tsx (1)
src/stores/usePopupStore.ts (4)
  • usePopupStore (37-43)
  • ConfirmModalProps (7-12)
  • MoreMenuProps (14-18)
  • SnackbarProps (20-26)
src/hooks/usePopupActions.ts (1)
src/stores/usePopupStore.ts (4)
  • usePopupStore (37-43)
  • ConfirmModalProps (7-12)
  • MoreMenuProps (14-18)
  • SnackbarProps (20-26)
src/components/common/Modal/Snackbar.tsx (1)
src/stores/usePopupStore.ts (1)
  • SnackbarProps (20-26)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/App.tsx
  • src/pages/feed/FeedDetailPage.tsx
  • src/components/common/Modal/MoreMenu.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/components/common/Modal/PopupContainer.tsx (1)
src/stores/usePopupStore.ts (4)
  • usePopupStore (37-43)
  • ConfirmModalProps (7-12)
  • MoreMenuProps (14-18)
  • SnackbarProps (20-26)
src/hooks/usePopupActions.ts (1)
src/stores/usePopupStore.ts (4)
  • usePopupStore (37-43)
  • ConfirmModalProps (7-12)
  • MoreMenuProps (14-18)
  • SnackbarProps (20-26)
src/components/common/Modal/Snackbar.tsx (1)
src/stores/usePopupStore.ts (1)
  • SnackbarProps (20-26)
🔇 Additional comments (17)
src/components/feed/TotalFeed.tsx (1)

35-35: 올바른 식별자 변경이 적용되었습니다.

postId에서 feedId로의 일관된 변경이 적용되어 React의 key prop이 올바르게 업데이트되었습니다.

src/components/common/Post/PostBody.tsx (2)

84-84: 타입 일관성을 위한 올바른 prop 변경입니다.

postId에서 feedId로의 prop 이름 변경이 타입 정의와 일치합니다.


94-94: 내비게이션 경로가 새로운 식별자로 올바르게 업데이트되었습니다.

feedId를 사용한 경로 변경이 일관되게 적용되었습니다.

src/types/post.ts (1)

33-33: PostBodyProps 타입이 올바르게 업데이트되었습니다.

새로운 feedId 속성을 반영하여 타입이 정확히 수정되었습니다.

src/data/postData.ts (3)

16-16: 목 데이터가 새로운 식별자와 타입으로 올바르게 업데이트되었습니다.

postId에서 feedId로의 변경과 함께 number 타입이 일관되게 적용되었습니다.


34-34: 두 번째 목 데이터 객체도 올바르게 업데이트되었습니다.

일관된 식별자 변경이 적용되었습니다.


55-55: mockFeedPost 객체의 식별자가 올바르게 변경되었습니다.

새로운 feedId 속성과 number 타입이 정확히 적용되었습니다.

src/components/common/Post/PostFooter.tsx (4)

37-37: UI 개선을 위한 스타일 추가가 적절합니다.

.count 클래스에 gap: 2px 추가로 요소 간 간격이 개선되었습니다.


45-45: 인터페이스가 새로운 식별자 타입으로 올바르게 업데이트되었습니다.

postId: string에서 feedId: number로의 타입 변경이 정확히 적용되었습니다.


53-53: 컴포넌트 prop이 일관되게 업데이트되었습니다.

새로운 feedId prop 사용이 타입 정의와 일치합니다.


73-73: 내비게이션 경로가 새로운 식별자로 정확히 업데이트되었습니다.

feedId를 사용한 경로 변경이 다른 컴포넌트들과 일관되게 적용되었습니다.

src/hooks/usePopupActions.ts (2)

4-25: 훅 구현이 깔끔하고 타입 안전성이 잘 보장되어 있습니다.

usePopupActions 훅이 팝업 액션들을 잘 추상화하여 컴포넌트에서 사용하기 편리하게 만들어졌습니다. 각 팝업 타입별로 전용 함수를 제공하여 타입 안전성을 보장하고 있습니다.


7-17: 팝업 타입 문자열 일치 확인 완료

usePopupActions, PopupContainer, PopupType 정의에서
'confirm-modal', 'moremenu', 'snackbar'가 모두 동일하게 사용되고 있어 추가 수정이 필요 없습니다.

src/components/common/Modal/PopupContainer.tsx (1)

69-87: SnackbarWrapper의 스타일링이 잘 구현되어 있습니다.

pointer-events: none을 배경에 적용하고 자식 요소에 pointer-events: auto를 적용하여 스낵바는 상호작용 가능하면서도 배경 클릭은 허용하는 구조가 잘 설계되었습니다.

src/components/common/Modal/Snackbar.tsx (1)

31-56: 애니메이션과 반응형 스타일링이 잘 구현되어 있습니다.

CSS 트랜지션과 transform을 활용한 부드러운 애니메이션, 그리고 반응형 너비 설정이 적절하게 구현되었습니다.

src/stores/usePopupStore.ts (2)

28-35: 타입 정의가 잘 구조화되어 있습니다.

PopupState 인터페이스가 명확하게 정의되어 있고, 제네릭 타입들이 적절히 사용되었습니다.


6-26: Props 인터페이스들이 명확하게 정의되어 있습니다.

각 팝업 타입별로 필요한 props들이 적절히 정의되어 있고, 선택적 속성과 필수 속성이 잘 구분되어 있습니다.

Comment on lines +12 to +18
useEffect(() => {
// 모달일 때만 스크롤 차단
document.body.style.overflow = popupType === 'snackbar' && isOpen ? '' : 'hidden';
return () => {
document.body.style.overflow = '';
};
}, [isOpen, popupType]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

스크롤 차단 로직에 오류가 있습니다.

14번 라인의 스크롤 차단 로직이 잘못되어 있습니다. 현재 코드는 스낵바가 열렸을 때만 스크롤을 허용하고, 다른 모든 경우에 차단하고 있습니다.

다음과 같이 수정해야 합니다:

-    document.body.style.overflow = popupType === 'snackbar' && isOpen ? '' : 'hidden';
+    document.body.style.overflow = isOpen && popupType !== 'snackbar' ? 'hidden' : '';
📝 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
useEffect(() => {
// 모달일 때만 스크롤 차단
document.body.style.overflow = popupType === 'snackbar' && isOpen ? '' : 'hidden';
return () => {
document.body.style.overflow = '';
};
}, [isOpen, popupType]);
useEffect(() => {
// 모달일 때만 스크롤 차단
document.body.style.overflow = isOpen && popupType !== 'snackbar'
? 'hidden'
: '';
return () => {
document.body.style.overflow = '';
};
}, [isOpen, popupType]);
🤖 Prompt for AI Agents
In src/components/common/Modal/PopupContainer.tsx around lines 12 to 18, the
scroll blocking logic is incorrect because it only allows scrolling when the
popupType is 'snackbar' and isOpen is true, blocking scroll in all other cases.
Fix this by changing the condition so that scrolling is blocked only when the
popup is open and the popupType is not 'snackbar'; otherwise, allow scrolling.
Adjust the assignment to document.body.style.overflow accordingly.

bookAuthor: string;
postContent: string;
postId: string;
feedId: number;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

타입 정의가 올바르게 업데이트되었습니다.

postId: string에서 feedId: number로의 변경이 적용되었습니다. 데이터 타입 변경이 전체 코드베이스에서 일관되게 적용되었는지 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# feedId가 number 타입으로 일관되게 사용되는지 확인
echo "=== feedId 사용 현황 확인 ==="
rg -A 3 -B 3 "feedId.*:" --type ts --type tsx

echo -e "\n=== postId 잔여 사용 확인 ==="
rg "postId" --type ts --type tsx

Length of output: 256


아직 feedId/postId 사용 현황을 정확히 파악하지 못했습니다. tsx 파일 인식을 위한 glob 옵션을 추가해 다시 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# .ts, .tsx 파일 모두에서 feedId/postId 검색
echo "=== feedId 사용 확인 (.ts/.tsx) ==="
rg "feedId" --glob '*.ts' --glob '*.tsx' -n

echo -e "\n=== postId 잔여 사용 확인 (.ts/.tsx) ==="
rg "postId" --glob '*.ts' --glob '*.tsx' -n

Length of output: 1315


postId 잔여 참조를 feedId로 교체하세요

타입 정의를 postId: stringfeedId: number로 변경하신 것은 올바르나, 아직 아래 파일에서 postId를 사용 중입니다. 일관되게 feedId로 교체해 주세요.

  • src/components/feed/MyFeed.tsx (38행)
    key={post.postId}key={post.feedId}
  • src/components/feed/OtherFeed.tsx (38행)
    key={post.postId}key={post.feedId}
🤖 Prompt for AI Agents
In src/types/post.ts at line 12, you have changed the type from postId to feedId
correctly, but in src/components/feed/MyFeed.tsx and
src/components/feed/OtherFeed.tsx at line 38, the code still uses post.postId.
Update these references to use post.feedId instead to maintain consistency with
the new type definition.

@heeeeyong heeeeyong merged commit ef2976b into develop Jul 23, 2025
2 of 3 checks passed
@heeeeyong heeeeyong deleted the feature/popup-hooks branch July 28, 2025 15:54
@coderabbitai coderabbitai bot mentioned this pull request Aug 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant