Skip to content

feat: 전체 피드 페이지 & 내 피드 페이지 구현#29

Merged
heeeeyong merged 16 commits intodevelopfrom
feature/feed
Jul 9, 2025
Merged

feat: 전체 피드 페이지 & 내 피드 페이지 구현#29
heeeeyong merged 16 commits intodevelopfrom
feature/feed

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Jul 6, 2025

#️⃣연관된 이슈

close #22

📝작업 내용 및 스크린샷

  1. Feed 페이지 추가 및 라우팅 설정
  2. 전체 피드 탭 페이지 및 컴포넌트 구현 완료
image 2. 내 피드 탭 페이지 및 컴포넌트 구현 완료 image

💬리뷰 요구사항

  1. 현재 Feed 탭 바 상태('피드' or '내 피드')에 따라서, [FeedPost.tsx의 PostHeader 유무] & [PostFooter.tsx에서의 저장하기 아이콘 or 공개(아이콘X)/비공개 아이콘]이 결정되게끔 코드를 구현하고자 합니다. 상태관리 라이브러리를 사용하는게 좋은 방법일지 궁금합니다.
  2. post.ts에 타입을 한번만 선언하고, 재사용하려고 하는데 지금처럼 사용하면 괜찮은 방법일지 궁금합니다.

제안사항

전역에 기본적으로 스크롤 바를 가리는 속성을 넣고, 특정 컴포넌트에서 스크롤 바가 필요하다면 속성을 넣어서 쓰는걸로 하는게 어떨까요?

추가 수정작업 (7/6~7/7 내 마무리)

  • 내 피드인 경우, save 아이콘 -> private 아이콘으로 변경
  • 내 피드 userProfile, 팔로워 이미지 리스트 추가
  • CSS 폰트, 색상 전역변수로 수정
  • 내 구독이 없는 경우, 컴포넌트 반영 (디자인 결정되면 완료할 예정)

Summary by CodeRabbit

  • New Features

    • 피드 및 내 피드 탭 인터페이스와 라우팅이 추가되었습니다.
    • 피드 화면에 프로필, 팔로우 리스트, 게시글 목록, 탭 바, 내비게이션 바 등 다양한 컴포넌트가 도입되었습니다.
    • 게시글 본문, 헤더, 푸터 등 상세 UI와 좋아요, 저장, 댓글 이동 등 상호작용 기능이 구현되었습니다.
    • 팔로워 리스트와 내 프로필 요약 섹션이 추가되었습니다.
    • 책 정보 카드 컴포넌트가 새롭게 도입되었습니다.
  • Style

    • 전체적으로 색상, 폰트, 레이아웃 스타일이 CSS 변수로 일관성 있게 개선되었습니다.
  • Bug Fixes

    • 일부 아이콘 경로가 올바르게 수정되었습니다.
  • Documentation

    • 타입 정의가 추가되어 컴포넌트의 props 구조가 명확해졌습니다.

@heeeeyong heeeeyong added ✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링 labels Jul 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 6, 2025

"""

Walkthrough

피드(Feed) 페이지의 전체 피드와 내 피드 탭 UI가 새로 구현되었습니다. 주요 컴포넌트와 타입 정의가 추가되었으며, 라우터에 "feed" 경로가 등록되었습니다. 각종 피드 구성요소(포스트, 헤더, 바디, 푸터, 팔로우 리스트, 프로필 등)가 세분화된 컴포넌트로 작성되었습니다.

Changes

파일/경로 요약 변경 내용 요약
src/components/feed/FeedPost.tsx, PostHeader.tsx, PostBody.tsx, PostFooter.tsx 피드 포스트 및 하위 구성요소(헤더, 바디, 푸터) 컴포넌트 신설 및 타입 정의
src/components/feed/FollowList.tsx, MyProfile.tsx, MyFollower.tsx 팔로우 리스트, 내 프로필, 팔로워 컴포넌트 신설
src/components/feed/MyFeed.tsx, TotalFeed.tsx 내 피드, 전체 피드 컴포넌트 신설 및 리스트 렌더링
src/components/feed/TabBar.tsx 탭바 컴포넌트 신설(전체/내 피드 탭 전환)
src/components/feed/BookInfoCard.tsx 도서 정보 카드 컴포넌트 신설
src/pages/feed/Feed.tsx 피드 페이지 신설, 탭 전환 및 각 피드 컴포넌트 연결, 목업 데이터 적용
src/pages/index.tsx "feed" 경로 라우팅 추가
src/types/post.ts 피드/포스트 관련 타입(PostData, FeedListProps, FeedPostProps) 신설
src/components/common/NavBar.tsx, TitleHeader.tsx CSS 변수 기반 스타일링으로 색상, 폰트 등 일괄 변경(기능 변경 없음)
src/pages/login/Login.tsx, src/pages/signup/Signup.styled.ts CSS 변수 기반 스타일링으로 색상, 폰트 등 일괄 변경(기능 변경 없음)
src/pages/signup/SignupDone.tsx, SignupGenre.tsx SVG 경로 변경(기능 변경 없음)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant FeedPage
    participant TabBar
    participant TotalFeed
    participant MyFeed
    participant FeedPost

    User->>FeedPage: "feed" 경로 진입
    FeedPage->>TabBar: 렌더링(activeTab)
    User->>TabBar: 탭 클릭(전체/내 피드)
    TabBar-->>FeedPage: onTabClick(tab)
    FeedPage->>TotalFeed: 전체 피드 탭 선택 시 posts 전달
    FeedPage->>MyFeed: 내 피드 탭 선택 시 posts 전달
    loop 각 포스트
        TotalFeed->>FeedPost: 포스트 데이터 전달
        MyFeed->>FeedPost: 포스트 데이터 전달
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
전체 피드 탭 구현 (#22)
내 피드 탭 구현 (#22)

Poem

(\(\
( -.-)
o_(")(")

피드 탭이 반짝,
내 피드도 짝짝!
토끼는 신나서
포스트를 척척,
팔로우와 프로필도
귀엽게 쏙쏙!

🥕✨
"""


📜 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 e2c8a79 and 97b73f6.

⛔ Files ignored due to path filters (1)
  • src/assets/feed/character.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • src/components/feed/BookInfoCard.tsx (1 hunks)
  • src/components/feed/FollowList.tsx (1 hunks)
  • src/components/feed/MyFollower.tsx (1 hunks)
  • src/components/feed/MyProfile.tsx (1 hunks)
  • src/components/feed/PostBody.tsx (1 hunks)
  • src/components/feed/PostHeader.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/components/feed/MyFollower.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/feed/MyProfile.tsx
  • src/components/feed/BookInfoCard.tsx
  • src/components/feed/PostHeader.tsx
  • src/components/feed/PostBody.tsx
  • src/components/feed/FollowList.tsx
✨ 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: 11

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

5-20: 향후 하드코딩된 데이터를 props나 API 호출로 교체하는 것을 고려해주세요.

현재 하드코딩된 팔로워 데이터는 개발 단계에서는 적절하지만, 향후 실제 데이터로 교체하는 것이 필요합니다.

향후 다음과 같은 방식으로 리팩토링을 고려해보세요:

  • 컴포넌트 props로 팔로워 데이터 전달
  • API 호출을 통한 동적 데이터 로딩
  • 로딩 상태 및 에러 처리 추가
src/components/feed/MyFeed.tsx (1)

6-10: 전역 CSS 변수 사용을 고려해보세요.

하드코딩된 패딩 값들을 전역 CSS 변수로 대체하는 것을 고려해보세요. PR 목표에서 언급된 것처럼 글로벌 변수를 사용하여 CSS 폰트와 색상을 업데이트하는 것이 일관성 있는 스타일링을 위해 중요합니다.

const Container = styled.div`
-  padding-top: 136px;
-  padding-bottom: 155px;
+  padding-top: var(--spacing-header-offset, 136px);
+  padding-bottom: var(--spacing-navbar-offset, 155px);
  background-color: var(--color-black-main);
`;
src/pages/feed/Feed.tsx (1)

9-39: 목업 데이터를 별도 파일로 분리하는 것을 고려해보세요.

목업 데이터가 컴포넌트 내부에 하드코딩되어 있어 컴포넌트가 복잡해 보입니다. 별도의 mock 파일로 분리하여 재사용성과 유지보수성을 높이는 것을 권장합니다.

// src/mocks/feedMocks.ts
export const mockPosts: PostData[] = [
  // 목업 데이터들...
];

그리고 import하여 사용:

+import { mockPosts } from '../../mocks/feedMocks';
-const mockPosts: PostData[] = [
-  // 목업 데이터들...
-];
src/components/feed/PostHeader.tsx (1)

54-56: titleColor prop 사용 최적화를 고려해보세요.

titleColor prop이 전달되지만 기본 CSS에서 이미 색상이 정의되어 있습니다. 색상 커스터마이징이 필요한 경우가 많지 않다면, CSS 변수를 활용하는 것이 더 일관성 있을 수 있습니다.

- titleColor: string;
+ titleColor?: string;

그리고 스타일 적용을 조건부로 변경:

- <div className="usertitle" style={{ color: titleColor }}>
+ <div className="usertitle" style={titleColor ? { color: titleColor } : undefined}>
src/components/feed/PostFooter.tsx (1)

62-64: 내비게이션 에러 처리 추가를 고려해보세요.

내비게이션 실패 시 사용자에게 피드백을 제공하는 것이 좋습니다.

const handleComment = () => {
-  navigate(`/feed/${postId}`);
+  try {
+    navigate(`/feed/${postId}`);
+  } catch (error) {
+    console.error('댓글 페이지로 이동 중 오류 발생:', error);
+    // 사용자에게 에러 메시지 표시
+  }
};
src/components/feed/PostBody.tsx (4)

23-23: 하드코딩된 값을 상수로 추출하는 것을 고려해보세요.

UI 컴포넌트에서 하드코딩된 픽셀 값들을 상수로 추출하면 유지보수성이 향상됩니다.

+ const BOOK_SECTION_MAX_WIDTH = 340;
+ const AUTHOR_NAME_MAX_WIDTH = 100;

  .left {
    overflow: hidden;
-   max-width: 340px;
+   max-width: ${BOOK_SECTION_MAX_WIDTH}px;
    // ...
  }

  .name {
-   max-width: 100px;
+   max-width: ${AUTHOR_NAME_MAX_WIDTH}px;
    // ...
  }

Also applies to: 46-46


69-69: 라인 클램프 값을 props로 받는 것을 고려해보세요.

현재 이미지 여부에 따라 라인 수가 고정되어 있습니다. 더 유연한 구현을 위해 props로 받는 것을 고려해보세요.

interface PostBodyProps {
  bookTitle: string;
  bookAuthor: string;
  postContent: string;
  postId: string;
  images?: string[];
  tags?: string[];
+ maxLines?: { withImage: number; withoutImage: number };
}

const PostBody = ({
  // ... other props
+ maxLines = { withImage: 3, withoutImage: 8 },
}: PostBodyProps) => {
  // ...
  
-  -webkit-line-clamp: ${({ hasImage }) => (hasImage ? 3 : 8)};
+  -webkit-line-clamp: ${({ hasImage }) => (hasImage ? maxLines.withImage : maxLines.withoutImage)};

139-139: 코드 내 주석을 영어로 작성하는 것을 고려해보세요.

코드의 국제화와 팀 협업을 고려하여 주석을 영어로 작성하는 것이 좋습니다.

- // API 연동시 경로 수정 필요
+ // TODO: Update path when API integration is implemented

82-82: CSS 주석을 영어로 작성하는 것을 고려해보세요.

CSS 내 주석도 코드 일관성을 위해 영어로 작성하는 것이 좋습니다.

- flex-shrink: 0; //고정사이즈
+ flex-shrink: 0; /* Fixed size */
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bc80a06 and 88941ed.

⛔ Files ignored due to path filters (9)
  • src/assets/common/rightArrow.svg is excluded by !**/*.svg
  • src/assets/feed/activeLike.svg is excluded by !**/*.svg
  • src/assets/feed/activeSave.svg is excluded by !**/*.svg
  • src/assets/feed/comment.svg is excluded by !**/*.svg
  • src/assets/feed/like.svg is excluded by !**/*.svg
  • src/assets/feed/people.svg is excluded by !**/*.svg
  • src/assets/feed/private.svg is excluded by !**/*.svg
  • src/assets/feed/rightArrow.svg is excluded by !**/*.svg
  • src/assets/feed/save.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • src/components/feed/FeedPost.tsx (1 hunks)
  • src/components/feed/FollowList.tsx (1 hunks)
  • src/components/feed/MyFeed.tsx (1 hunks)
  • src/components/feed/MyProfile.tsx (1 hunks)
  • src/components/feed/PostBody.tsx (1 hunks)
  • src/components/feed/PostFooter.tsx (1 hunks)
  • src/components/feed/PostHeader.tsx (1 hunks)
  • src/components/feed/TabBar.tsx (1 hunks)
  • src/components/feed/TotalFeed.tsx (1 hunks)
  • src/pages/feed/Feed.styled.ts (1 hunks)
  • src/pages/feed/Feed.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/types/post.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/pages/feed/Feed.tsx (2)
src/types/post.ts (1)
  • PostData (1-15)
src/pages/feed/Feed.styled.ts (1)
  • Container (3-7)
src/components/feed/MyProfile.tsx (1)
src/pages/feed/Feed.styled.ts (1)
  • Container (3-7)
src/components/feed/TotalFeed.tsx (1)
src/types/post.ts (1)
  • PostData (1-15)
src/components/feed/FeedPost.tsx (2)
src/pages/feed/Feed.styled.ts (1)
  • Container (3-7)
src/types/post.ts (1)
  • PostData (1-15)
src/components/feed/PostBody.tsx (1)
src/pages/feed/Feed.styled.ts (1)
  • Container (3-7)
src/components/feed/TabBar.tsx (1)
src/pages/feed/Feed.styled.ts (1)
  • Container (3-7)
🔇 Additional comments (11)
src/types/post.ts (1)

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

PostData 인터페이스가 명확하고 포괄적으로 정의되어 있습니다. 필수 필드와 선택적 필드가 적절히 분리되어 있고, 네이밍 컨벤션도 일관성 있게 적용되어 있습니다.

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

1-65: 잘 구성된 TabBar 컴포넌트입니다.

타입 정의가 명확하고, 스타일링이 일관성 있게 적용되어 있습니다. 활성/비활성 상태에 따른 스타일 변경도 적절히 구현되어 있습니다.

src/pages/feed/Feed.styled.ts (1)

1-7: 간결하고 명확한 스타일링입니다.

반응형 너비 제약과 중앙 정렬이 적절히 적용되어 있습니다. 다른 컴포넌트들과 일관된 스타일링 패턴을 따르고 있습니다.

src/pages/index.tsx (1)

12-12: 라우트 추가가 적절히 구현되었습니다.

새로운 Feed 컴포넌트의 임포트와 라우트 설정이 기존 구조와 일관성 있게 적용되어 있습니다.

Also applies to: 24-24

src/components/feed/MyFeed.tsx (2)

12-16: 타입 정의가 잘 되어 있습니다.

props의 타입 정의가 명확하고 PostData 인터페이스를 적절히 활용하고 있습니다.


17-25: 컴포넌트 구조와 로직이 깔끔합니다.

기본값 설정과 map 함수의 key 사용이 올바르게 구현되어 있습니다. 컴포넌트 분리도 적절합니다.

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

41-46: 탭 전환 시 스크롤 처리가 적절합니다.

사용자 경험을 위한 스크롤 처리가 잘 구현되어 있습니다.

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

17-25: 컴포넌트 로직은 적절합니다.

기본값 설정과 map 함수 사용이 올바르게 구현되어 있습니다.

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

5-105: 스타일링 구조가 잘 구성되어 있습니다.

CSS 변수를 활용한 스타일링과 반응형 레이아웃이 잘 구현되어 있습니다. 다만 PR 목표에서 언급된 대로 전역 CSS 변수로 통일하는 작업이 필요합니다.

src/components/feed/FeedPost.tsx (2)

27-29: 타입 정의가 우수합니다.

PostData 인터페이스와 교집합 타입을 활용한 타입 정의가 적절하고 타입 안전성을 잘 보장하고 있습니다.


31-43: 조건부 렌더링과 컴포넌트 구성이 훌륭합니다.

showHeader prop을 통한 조건부 렌더링과 컴포넌트 분리가 적절하게 구현되어 있습니다.

Comment on lines 48 to 59
return (
<Container>
{/* MainHeader.tsx */}
<TabBar activeTab={activeTab} onTabClick={setActiveTab} />
{activeTab === '피드' ? (
<TotalFeed showHeader={true} posts={mockPosts} />
) : (
<MyFeed showHeader={false} posts={mockPosts} />
)}
<NavBar />
</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

상태 관리 라이브러리 사용을 고려해보세요.

PR 목표에서 언급하신 것처럼 탭 바 상태를 제어하기 위한 상태 관리 라이브러리 사용을 고려해보세요. 현재 구조에서는 showHeader prop이 하드코딩되어 있어 유연성이 부족합니다.

// Context 또는 상태 관리 라이브러리 사용 예시
const { activeTab, showHeader } = useFeedState();
🤖 Prompt for AI Agents
In src/pages/feed/Feed.tsx around lines 48 to 59, the activeTab state and
showHeader prop are currently managed locally and hardcoded, limiting
flexibility. Refactor the component to use a state management library or React
context to handle activeTab and showHeader states globally. Replace local state
and hardcoded props with values obtained from the shared state (e.g.,
useFeedState hook) to improve state management and component reusability.

Comment on lines 111 to 115
<img src="https://placehold.co/54x54" />
<div className="userInfo">
<div className="username">userName</div>
<div className="usertitle">userTitle</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 데이터를 props로 받도록 개선해보세요.

현재 모든 사용자 정보가 하드코딩되어 있습니다. 실제 사용자 데이터를 props로 받아 동적으로 렌더링하도록 개선이 필요합니다.

interface MyProfileProps {
  profileImgUrl: string;
  userName: string;
  userTitle: string;
  followerCount: number;
  postCount: number;
}

const MyProfile = ({ profileImgUrl, userName, userTitle, followerCount, postCount }: MyProfileProps) => {
  // ...
};
🤖 Prompt for AI Agents
In src/components/feed/MyProfile.tsx around lines 111 to 115, the user profile
data is hardcoded. Refactor the component to accept profileImgUrl, userName,
userTitle, followerCount, and postCount as props according to the provided
MyProfileProps interface, and use these props to dynamically render the user
information instead of static values.

return (
<Container>
<div className="userProfile">
<img src="https://placehold.co/54x54" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 접근성을 개선해보세요.

이미지에 적절한 alt 텍스트가 없습니다. 스크린 리더 사용자를 위해 alt 속성을 추가해주세요.

-<img src="https://placehold.co/54x54" />
+<img src="https://placehold.co/54x54" alt="사용자 프로필 이미지" />
📝 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
<img src="https://placehold.co/54x54" />
<img src="https://placehold.co/54x54" alt="사용자 프로필 이미지" />
🤖 Prompt for AI Agents
In src/components/feed/MyProfile.tsx at line 111, the img tag lacks an alt
attribute, which is important for accessibility. Add a meaningful alt attribute
to the img element to describe the image content for screen reader users.

Comment on lines +39 to +41
<BorderBottom />
{/* 페이징 처리에서 마지막 게시글인 경우 BorderBottom 안보이게 처리해야함 */}
</>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

BorderBottom 처리 로직을 구현해보세요.

주석에서 언급한 것처럼 페이지네이션 처리에서 마지막 게시글의 BorderBottom을 숨기는 로직이 필요합니다. 이를 구현하기 위해 추가적인 prop이 필요할 것 같습니다.

export type FeedPostProps = PostData & {
  showHeader: boolean;
  isLast?: boolean;
};

const FeedPost = ({ showHeader, isLast, ...postData }: FeedPostProps) => {
  return (
    <>
      <Container>
        {showHeader && <PostHeader {...postData} />}
        <PostBody {...postData} />
        <PostFooter {...postData} />
      </Container>
      {!isLast && <BorderBottom />}
    </>
  );
};
🤖 Prompt for AI Agents
In src/components/feed/FeedPost.tsx around lines 39 to 41, the BorderBottom
component should not be shown for the last post in pagination. To fix this, add
an optional boolean prop isLast to FeedPostProps, then conditionally render
BorderBottom only if isLast is false or undefined. Update the component to
destructure isLast and use {!isLast && <BorderBottom />} to hide the border for
the last post.

return (
<Container>
<div className="headerInfo">
<img src={profileImgUrl} alt="칭호 이미지" />
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 로딩 에러 처리 및 접근성 개선이 필요합니다.

프로필 이미지에 대한 에러 처리와 접근성이 부족합니다:

  • 이미지 로딩 실패 시 대체 이미지나 기본 아바타 표시
  • alt 텍스트의 국제화 고려
  • 이미지 로딩 상태 처리

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

- <img src={profileImgUrl} alt="칭호 이미지" />
+ <img 
+   src={profileImgUrl} 
+   alt={`${userName}의 프로필 이미지`}
+   onError={(e) => {
+     (e.target as HTMLImageElement).src = '/default-avatar.png';
+   }}
+ />
📝 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
<img src={profileImgUrl} alt="칭호 이미지" />
<img
src={profileImgUrl}
alt={`${userName}의 프로필 이미지`}
onError={(e) => {
(e.target as HTMLImageElement).src = '/default-avatar.png';
}}
/>
🤖 Prompt for AI Agents
In src/components/feed/PostHeader.tsx at line 63, the img element lacks error
handling and accessibility improvements. To fix this, implement an onError
handler to replace the image source with a default avatar if loading fails,
update the alt attribute to use internationalized text for better accessibility,
and consider adding loading state management to enhance user experience during
image loading.

Comment on lines +70 to +71
<img src={liked ? activeLike : like} onClick={handleLike} />
<div>{likeCount}</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 접근성 개선이 필요합니다.

모든 이미지에 alt 속성이 누락되어 있습니다. 스크린 리더 사용자를 위해 적절한 alt 텍스트를 추가해야 합니다.

- <img src={liked ? activeLike : like} onClick={handleLike} />
+ <img 
+   src={liked ? activeLike : like} 
+   alt={liked ? "좋아요 취소" : "좋아요"} 
+   onClick={handleLike} 
+ />
- <img src={comment} onClick={handleComment} />
+ <img src={comment} alt="댓글" onClick={handleComment} />
- <img src={saved ? activeSave : save} onClick={handleSave} />
+ <img 
+   src={saved ? activeSave : save} 
+   alt={saved ? "저장 취소" : "저장"} 
+   onClick={handleSave} 
+ />

Also applies to: 74-75, 78-78

🤖 Prompt for AI Agents
In src/components/feed/PostFooter.tsx around lines 70-71, 74-75, and 78, the img
elements lack alt attributes, which reduces accessibility for screen reader
users. Add meaningful alt text to each img tag that describes the image content
or function, such as "like icon" or "active like icon," to improve accessibility
compliance.

Comment on lines +53 to +56
const handleLike = () => {
setLiked(!liked);
setLikeCount(prev => (liked ? prev - 1 : prev + 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

좋아요 카운트 업데이트 로직 개선이 필요합니다.

현재 구현은 빠른 클릭 시 race condition이 발생할 수 있습니다. 함수형 업데이트를 사용하는 것이 더 안전합니다.

const handleLike = () => {
-  setLiked(!liked);
-  setLikeCount(prev => (liked ? prev - 1 : prev + 1));
+  setLiked(prevLiked => {
+    const newLiked = !prevLiked;
+    setLikeCount(prevCount => newLiked ? prevCount + 1 : prevCount - 1);
+    return newLiked;
+  });
};
📝 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 handleLike = () => {
setLiked(!liked);
setLikeCount(prev => (liked ? prev - 1 : prev + 1));
};
const handleLike = () => {
setLiked(prevLiked => {
const newLiked = !prevLiked;
setLikeCount(prevCount => newLiked ? prevCount + 1 : prevCount - 1);
return newLiked;
});
};
🤖 Prompt for AI Agents
In src/components/feed/PostFooter.tsx around lines 53 to 56, the like count
update logic can cause race conditions on rapid clicks because it uses the
current liked state directly. Refactor the handleLike function to use functional
updates for both setLiked and setLikeCount, ensuring the new state is based on
the previous state to avoid inconsistencies during quick successive updates.

Comment on lines 157 to 159
{images.map((src, i) => (
<img key={i} src={src} />
))}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이미지 접근성 및 에러 처리 개선이 필요합니다.

이미지 배열에 대한 접근성과 에러 처리가 부족합니다.

{images.map((src, i) => (
-  <img key={i} src={src} />
+  <img 
+    key={i} 
+    src={src} 
+    alt={`게시글 이미지 ${i + 1}`}
+    onError={(e) => {
+      (e.target as HTMLImageElement).style.display = 'none';
+    }}
+  />
))}
📝 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
{images.map((src, i) => (
<img key={i} src={src} />
))}
{images.map((src, i) => (
<img
key={i}
src={src}
alt={`게시글 이미지 ${i + 1}`}
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
))}
🤖 Prompt for AI Agents
In src/components/feed/PostBody.tsx around lines 157 to 159, the image elements
lack accessibility features and error handling. Add meaningful alt attributes to
each img tag for better accessibility, possibly using descriptive text or
placeholders. Also, implement error handling for image loading failures by
adding an onError handler to manage or replace broken images gracefully.

Copy link
Collaborator

@ho0010 ho0010 left a comment

Choose a reason for hiding this comment

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

LGTM!

리뷰 요구사항
현재 Feed 탭 바 상태('피드' or '내 피드')에 따라서, [FeedPost.tsx의 PostHeader 유무] & [PostFooter.tsx에서의 저장하기 아이콘 or 공개(아이콘X)/비공개 아이콘]이 결정되게끔 코드를 구현하고자 합니다. 상태관리 라이브러리를 사용하는게 좋은 방법일지 궁금합니다.

그렇게 해도 좋을 것 같습니다. React 내장 훅인 useState 사용하면 좋을 것 같은데 언급하신 상태관리 라이브러리는 외부 라이브러리를 말씀하시는 건가요?? 아니면 API 연동이후를 말씀하시는건가요?

post.ts에 타입을 한번만 선언하고, 재사용하려고 하는데 지금처럼 사용하면 괜찮은 방법일지 궁금합니다.

넵 좋은 것 같습니다. 저도 저렇게 분리해야겠네요~

제안사항
전역에 기본적으로 스크롤 바를 가리는 속성을 넣고, 특정 컴포넌트에서 스크롤 바가 필요하다면 속성을 넣어서 쓰는걸로 하는게 어떨까요?

동의합니다 요구사항 전반을 살펴보았을때 스크롤 바가 필요한 부분은 거의 없어 보입니다

공통적인 코드리뷰 사항이 있습니다!
위에 표시한 부분처럼 뎁스가 너무 깊어지지 않게 스타일링 분리를 하는게 더 유지보수하기 좋을 것 같습니다!

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

♻️ Duplicate comments (1)
src/components/feed/TabBar.tsx (1)

49-63: 이전 리뷰 의견 반영 완료 및 접근성 개선 제안

이전 리뷰에서 제안된 범용적인 탭 컴포넌트로 잘 구현되었습니다. 탭 배열을 받아 동적으로 렌더링하므로 재사용성이 높습니다.

접근성 개선을 위해 다음과 같은 추가 속성들을 고려해보세요:

 const TabBar = ({ tabs, activeTab, onTabClick }: TabProps) => {
   return (
-    <Container>
+    <Container role="tablist">
       {tabs.map(tab => (
         <TabButton
           key={tab}
           className={activeTab === tab ? 'active' : 'inactive'}
           onClick={() => onTabClick(tab)}
+          role="tab"
+          aria-selected={activeTab === tab}
+          tabIndex={0}
+          onKeyDown={(e) => {
+            if (e.key === 'Enter' || e.key === ' ') {
+              onTabClick(tab);
+            }
+          }}
         >
           {tab}
         </TabButton>
       ))}
     </Container>
   );
 };
🧹 Nitpick comments (1)
src/components/feed/TabBar.tsx (1)

19-41: 접근성 및 사용자 경험 개선

현재 구현은 잘 되어 있지만, 접근성과 사용자 경험을 위한 몇 가지 개선사항이 있습니다.

다음과 같은 개선을 고려해보세요:

 const TabButton = styled.div`
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   text-align: center;
   padding: 8px 4px;
   font-size: var(--font-size-lg);
   cursor: pointer;
+  transition: all 0.2s ease;
+  
+  &:hover {
+    opacity: 0.8;
+  }

   &.active {
     color: var(--color-white);
     border-bottom: 2px solid var(--color-white);
     font-weight: var(--font-weight-semibold);
     line-height: 24px;
   }
   &.inactive {
     color: var(--color-grey-300);
     border-bottom: 2px solid transparent;
     font-weight: var(--font-weight-medium);
     line-height: 24px;
   }
 `;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f0f436a and 262916c.

📒 Files selected for processing (2)
  • src/components/feed/TabBar.tsx (1 hunks)
  • src/pages/feed/Feed.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/feed/Feed.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/feed/TabBar.tsx (1)
src/pages/signup/Signup.styled.ts (1)
  • Container (3-165)
🔇 Additional comments (2)
src/components/feed/TabBar.tsx (2)

43-47: 타입 정의 개선

인터페이스 정의가 명확하고 잘 구성되어 있습니다.


65-66: 표준 내보내기 구문

표준적인 내보내기 방식이 잘 적용되었습니다.

Comment on lines +3 to +17
const Container = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
flex-direction: row;
margin: 0 auto;

min-width: 320px;
max-width: 767px;
gap: 20px;
padding: 88px 20px 0 20px;
background-color: var(--color-black-main);
`;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

고정 위치 사용 시 z-index 및 레이아웃 고려사항

고정 위치를 사용할 때 다른 요소들과의 겹침을 방지하기 위해 z-index를 명시적으로 설정하는 것이 좋습니다. 또한 margin: 0 auto는 고정 위치에서 중앙 정렬에 효과적이지 않을 수 있습니다.

다음과 같이 개선을 제안합니다:

 const Container = styled.div`
   position: fixed;
   top: 0;
   left: 0;
   right: 0;
+  left: 50%;
+  transform: translateX(-50%);
   display: flex;
   flex-direction: row;
-  margin: 0 auto;
+  z-index: 1000;

   min-width: 320px;
   max-width: 767px;
   gap: 20px;
   padding: 88px 20px 0 20px;
   background-color: var(--color-black-main);
 `;
📝 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 Container = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
flex-direction: row;
margin: 0 auto;
min-width: 320px;
max-width: 767px;
gap: 20px;
padding: 88px 20px 0 20px;
background-color: var(--color-black-main);
`;
const Container = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: row;
z-index: 1000;
min-width: 320px;
max-width: 767px;
gap: 20px;
padding: 88px 20px 0 20px;
background-color: var(--color-black-main);
`;
🤖 Prompt for AI Agents
In src/components/feed/TabBar.tsx lines 3 to 17, the Container styled component
uses fixed positioning without specifying a z-index, which can cause overlap
issues with other elements, and margin: 0 auto is ineffective for centering
fixed elements. To fix this, add an explicit z-index value to ensure proper
stacking order and remove or replace margin: 0 auto with appropriate centering
techniques for fixed elements, such as using left and right with auto margins or
flexbox on a parent container.

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

🧹 Nitpick comments (3)
src/components/feed/MyFeed.tsx (3)

6-10: 매직 넘버를 상수나 CSS 변수로 대체하는 것을 고려해보세요.

현재 고정된 패딩 값들(136px, 155px)은 반응형 디자인에 문제가 될 수 있습니다. 이러한 값들을 CSS 변수나 상수로 정의하여 일관성을 유지하는 것이 좋겠습니다.

const Container = styled.div`
-  padding-top: 136px;
-  padding-bottom: 155px;
+  padding-top: var(--spacing-feed-top, 136px);
+  padding-bottom: var(--spacing-feed-bottom, 155px);
  background-color: var(--color-black-main);
`;

16-18: props 스프레딩 시 의도하지 않은 속성 전달을 주의하세요.

{...post} 스프레딩은 편리하지만, post 객체에 FeedPost 컴포넌트가 예상하지 않는 속성들이 포함될 수 있습니다. 필요한 속성만 명시적으로 전달하는 것을 고려해보세요.

{posts.map(post => (
-  <FeedPost key={post.postId} showHeader={showHeader} isMyFeed={isMyFeed} {...post} />
+  <FeedPost 
+    key={post.postId} 
+    showHeader={showHeader} 
+    isMyFeed={isMyFeed}
+    postId={post.postId}
+    // 필요한 다른 속성들 명시적으로 전달
+  />
))}

16-18: 빈 배열 처리를 위한 UI 개선을 고려해보세요.

현재 posts 배열이 비어있을 때 아무것도 렌더링되지 않습니다. 사용자 경험을 위해 "아직 작성한 피드가 없습니다"와 같은 메시지를 표시하는 것이 좋겠습니다.

<Container>
  <MyProfile />
-  {posts.map(post => (
-    <FeedPost key={post.postId} showHeader={showHeader} isMyFeed={isMyFeed} {...post} />
-  ))}
+  {posts.length > 0 ? (
+    posts.map(post => (
+      <FeedPost key={post.postId} showHeader={showHeader} isMyFeed={isMyFeed} {...post} />
+    ))
+  ) : (
+    <EmptyState>아직 작성한 피드가 없습니다.</EmptyState>
+  )}
</Container>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cb27f5c and 73996af.

⛔ Files ignored due to path filters (1)
  • src/assets/feed/lockIcon.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • src/components/feed/FeedPost.tsx (1 hunks)
  • src/components/feed/MyFeed.tsx (1 hunks)
  • src/components/feed/PostFooter.tsx (1 hunks)
  • src/components/feed/TotalFeed.tsx (1 hunks)
  • src/pages/feed/Feed.tsx (1 hunks)
  • src/types/post.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/feed/TotalFeed.tsx
  • src/components/feed/FeedPost.tsx
  • src/pages/feed/Feed.tsx
  • src/components/feed/PostFooter.tsx
  • src/types/post.ts
🔇 Additional comments (1)
src/components/feed/MyFeed.tsx (1)

12-12: 타입 정의와 기본값 설정이 잘 되어 있습니다.

FeedListProps 타입을 사용하고 posts 배열에 기본값을 제공한 것은 좋은 접근입니다.

@heeeeyong heeeeyong merged commit 170f584 into develop Jul 9, 2025
1 check passed
@heeeeyong heeeeyong self-assigned this Jul 18, 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.

[UI] 피드 페이지 (전체, 내 피드)

3 participants