Skip to content

feat: 피드 글 상세보기 페이지#50

Merged
heeeeyong merged 14 commits intodevelopfrom
feature/feed
Jul 21, 2025
Merged

feat: 피드 글 상세보기 페이지#50
heeeeyong merged 14 commits intodevelopfrom
feature/feed

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Jul 19, 2025

#️⃣연관된 이슈

close #33 [UI] 피드 글 상세보기 페이지

📝작업 내용

  1. src/data 폴더를 만들어 더미데이터 관리
image image
2. postHeader.tsx type 추가(피드 글 헤더에 올 때의 사이즈와 댓글에 올 때의 사이즈가 달라서 type으로 조건부 스타일링이 가능하게 함)
3. 댓글(Reply.tsx) 및 답글(SubReply.tsx) 컴포넌트 추가
image image
5. 피드 글 상세보기 페이지(FeedDetailPage.tsx) 추가
6. MessageInput.tsx 로직 추가 - 빈 문자열 전송 제한 - 메세지 전송 후 textarea 높이 초기화 - placeholder props로 변경 (동적으로 사용)
7. 페이지 이동할 때 스크롤 위치 0으로 초기화 추가

💬리뷰 요구사항

스크롤 바 숨김처리는 전역으로 두는 것보다 필요한 부분에서만 속성쓰는게 나을거같다는 생각도 드는데 어떻게 생각하시나요?
전역으로 했더니 전체 브라우저 body의 스크롤바도 사라져서 좀 곤란하네요...
스크롤 바를 없애는 공통 styled 컴포넌트만 따로 뺄까도 생각해봤는데 그게 괜찮으려나요?

Summary by CodeRabbit

Summary by CodeRabbit

  • 신규 기능

    • 피드 상세 페이지(FeedDetailPage)와 댓글/답글 목록(ReplyList, Reply, SubReply) 컴포넌트가 추가되었습니다.
    • 피드 상세 페이지에서 댓글을 작성할 수 있습니다.
    • TitleHeader 컴포넌트에 커스텀 아이콘(rightIcon) 표시 기능이 추가되었습니다.
    • PostHeader 컴포넌트에 사용자 피드로 이동하는 클릭 기능이 추가되었습니다.
    • BookInfoCard 컴포넌트가 클릭 시 도서 상세 페이지로 이동하는 기능이 추가되었습니다.
    • 스크롤 위치를 상단으로 자동 이동시키는 ScrollToTop 컴포넌트가 추가되었습니다.
  • 개선 사항

    • MessageInput 컴포넌트에 placeholder 커스텀 기능과 입력값 유효성 검사(공백/빈값 방지)가 추가되었습니다.
    • Profile 컴포넌트의 구독 버튼이 내부 상태로 관리되며, 클릭 시 상태가 토글되고 라벨이 변경됩니다.
    • 피드 및 댓글 관련 컴포넌트의 import 경로가 일관성 있게 정리되었습니다.
    • PostBody 컴포넌트에 isbn 프로퍼티가 추가되고, 관련 UI가 업데이트되었습니다.
  • 버그 수정

    • 없음
  • 데이터

    • 피드 및 댓글용 목(mock) 데이터가 추가되었습니다.
  • 타입

    • 댓글/답글 구조를 위한 타입이 추가되고, 일부 기존 타입의 필수값이 선택값으로 변경되었습니다.
  • 라우팅

    • 피드 상세 페이지 라우트가 추가되고, 팔로워 리스트 라우트가 변경되었습니다.

@vercel
Copy link

vercel bot commented Jul 19, 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 20, 2025 11:17am

@coderabbitai
Copy link

coderabbitai bot commented Jul 19, 2025

"""

Walkthrough

이번 변경 사항에서는 피드 게시글 상세보기 페이지와 댓글(대댓글 포함) UI 컴포넌트가 새롭게 구현되었습니다. 댓글 및 대댓글 데이터 타입이 추가되고, 목업 데이터가 정의되었으며, 상세 페이지 라우트가 생성되었습니다. 일부 컴포넌트의 import 경로와 props가 수정되었습니다.

Changes

파일/그룹 변경 요약
src/components/common/Post/Reply.tsx, src/components/common/Post/SubReply.tsx, src/components/common/Post/ReplyList.tsx 댓글(Reply), 대댓글(SubReply), 댓글 리스트(ReplyList) 신규 컴포넌트 추가
src/pages/feed/FeedDetailPage.tsx 피드 게시글 상세보기 페이지 컴포넌트 신규 추가
src/types/post.ts 댓글, 대댓글 타입(ReplyDataProps, SubReplyDataProps, ReplyListProps) 추가 및 isMyFeed 옵션화, PostData에 userId, isbn 추가
src/data/postData.ts 피드 및 댓글 목업 데이터 추가
src/pages/index.tsx 상세페이지 라우트(feed/:feedId) 추가 및 FollowerListPage 라우트 수정
src/components/common/TitleHeader.tsx TitleHeader에 rightIcon prop 추가 및 렌더링 로직 개선
src/components/today-words/MessageInput.tsx placeholder prop 추가, 입력값 유효성 검사 및 동적 높이 리셋 기능 추가
src/components/feed/FeedPost.tsx PostBody, PostFooter, PostHeader import 경로 수정, isMyFeed boolean 변환 적용
src/components/common/Post/PostBody.tsx BookInfoCard import 경로 수정, isbn prop 추가 및 cursor:pointer 제거
src/components/common/Post/PostFooter.tsx SVG asset import 경로 수정
src/components/feed/TotalBar.tsx JSX 내 주석 한 줄 삭제 및 스타일 구조 변경
src/components/common/Post/PostHeader.tsx PostHeader 컴포넌트 리팩토링, 타입 추가 및 클릭 시 사용자 피드로 네비게이션 기능 추가
src/components/feed/BookInfoCard.tsx isbn prop 추가 및 클릭 시 도서 상세 페이지로 네비게이션 기능 추가
src/components/feed/Profile.tsx 팔로우 상태 관리 및 토글 기능 추가, 버튼 텍스트 변경 및 커서 스타일 추가
src/pages/feed/Feed.tsx, src/pages/feed/OtherFeedPage.tsx mockPosts 외부 모듈로 분리 및 import 변경
src/components/common/ScrollToTop.tsx 스크롤 위치를 최상단으로 초기화하는 컴포넌트 신규 추가
src/components/feed/FollowList.tsx followerList 가로 스크롤바 숨김 스타일 추가

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant FeedDetailPage
    participant FeedPost
    participant ReplyList
    participant MessageInput

    User->>FeedDetailPage: 상세 페이지 접근
    FeedDetailPage->>FeedPost: 피드 게시글 렌더링(목업 데이터)
    FeedDetailPage->>ReplyList: 댓글 리스트 렌더링(목업 데이터)
    User->>MessageInput: 댓글 입력 및 전송
    MessageInput->>FeedDetailPage: onSend 콜백 호출
    FeedDetailPage->>ReplyList: 댓글 리스트 갱신(예정)
Loading

Possibly related PRs

  • THIP-TextHip/THIP-Web#29: FeedPost 컴포넌트 및 관련 서브컴포넌트 최초 도입 PR로, 본 PR에서 import 경로 변경과 관련됨.
  • THIP-TextHip/THIP-Web#25: TitleHeader 컴포넌트에 rightIcon prop 추가 전 상태 관련 PR로, 본 PR과 TitleHeader 변경 부분에서 연관됨.

Poem

🐇
댓글이 달리고, 대댓글이 피네
상세한 피드에 토끼가 춤추네
아이콘도 반짝, 입력창도 새로워
목업 데이터 품고, 페이지가 완성되어
깡총깡총, 코드밭에 변화가 가득!
📝✨
"""


📜 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 0834f0f and bfae589.

📒 Files selected for processing (1)
  • src/components/feed/FollowList.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • 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.

@heeeeyong heeeeyong self-assigned this Jul 19, 2025
@heeeeyong heeeeyong added ✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링 labels Jul 19, 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: 3

♻️ Duplicate comments (3)
src/components/common/Post/Reply.tsx (3)

39-41: 접근성 개선이 필요합니다.

SubReply 컴포넌트와 동일한 접근성 문제가 있습니다. 좋아요 버튼에 적절한 접근성 속성이 필요합니다.

SubReply 컴포넌트에서 제안한 것과 동일한 접근성 개선사항을 적용해주세요.


37-37: "답글작성" 기능 구현이 누락되었습니다.

클릭 가능한 스타일이 적용되어 있지만 실제 기능이 없는 상태입니다.

SubReply 컴포넌트와 동일한 문제로, 답글 작성 기능 구현이 필요합니다.


89-89: 하드코딩된 폰트 크기를 수정하세요.

SubReply 컴포넌트와 동일한 문제로, 10px이 하드코딩되어 있습니다.

-      font-size: 10px;
+      font-size: ${typography.fontSize.xs};
🧹 Nitpick comments (12)
src/components/today-words/MessageInput.tsx (3)

47-47: 빈 메시지 전송 방지 로직이 적절히 추가되었습니다.

trim() 사용으로 공백만 있는 메시지도 적절히 차단됩니다. 하지만 코드 중복을 줄이기 위해 검증 로직을 별도 함수로 분리하는 것을 고려해보세요.

+ const isValidMessage = () => value.trim() !== '';
+
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      if (isComposing) {
        return;
      }
-     if (value.trim() === '') return;
+     if (!isValidMessage()) return;
      e.preventDefault();
      onSend();
      resetInputHeight();
    }
  };

  const handleSendClick = () => {
-   if (value.trim() === '') return;
+   if (!isValidMessage()) return;
    onSend();
    resetInputHeight();
  };

Also applies to: 58-58


50-53: 전송 후 높이 초기화 기능이 추가되었지만 코드 중복이 있습니다.

기능은 적절하지만 중복된 로직을 별도 함수로 분리하면 유지보수성이 향상됩니다.

+ const resetInputHeight = () => {
+   if (inputRef.current) {
+     inputRef.current.style.height = 'auto';
+   }
+ };

  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    // ... 기존 로직
    onSend();
-   if (inputRef.current) {
-     inputRef.current.style.height = 'auto';
-   }
+   resetInputHeight();
  };

  const handleSendClick = () => {
    // ... 기존 로직
    onSend();
-   if (inputRef.current) {
-     inputRef.current.style.height = 'auto';
-   }
+   resetInputHeight();
  };

Also applies to: 60-63


47-47: 한국어 주석의 오타를 수정해주세요.

"공백 메세지 전송 방지" → "공백 메시지 전송 방지"로 수정해야 합니다.

- if (value.trim() === '') return; // 공백 메세지 전송 방지
+ if (value.trim() === '') return; // 공백 메시지 전송 방지

Also applies to: 58-58

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

18-18: 더보기 버튼 기능이 구현되지 않았습니다.

현재 handleMoreClick 함수가 비어있습니다. 향후 기능 구현이 필요하거나 TODO 주석을 추가하는 것을 고려해보세요.

더보기 기능 구현이 필요하시면 도움을 드릴 수 있습니다. 새로운 이슈를 생성하시겠습니까?


47-58: 스타일링에서 일관성 개선이 필요합니다.

하드코딩된 배경색(#121212) 대신 전역 색상 상수를 사용하는 것이 좋겠습니다. 다른 컴포넌트들에서 colors.black.main을 사용하고 있습니다.

-  background-color: #121212;
+  background-color: ${colors.black.main};

그리고 colors import를 추가해야 합니다:

import styled from '@emotion/styled';
+import { colors } from '@/styles/global/global';
src/components/common/Post/ReplyList.tsx (2)

35-36: 반응형 디자인을 위한 최소 너비 검토가 필요합니다.

min-width: 360px가 설정되어 있는데, 이는 작은 모바일 기기에서 가로 스크롤을 유발할 수 있습니다. 320px 또는 더 작은 값으로 조정하는 것을 고려해보세요.

-  min-width: 360px;
+  min-width: 320px;

55-56: EmptyState에서도 동일한 최소 너비 이슈가 있습니다.

Container와 마찬가지로 min-width: 360px를 더 작은 값으로 조정하는 것이 좋겠습니다.

-  min-width: 360px;
+  min-width: 320px;
src/data/postData.ts (2)

14-14: 반복되는 텍스트 패턴을 개선해보세요.

현재 같은 문장이 13번 반복되어 있습니다. 실제 사용 시나리오를 더 잘 반영하는 다양한 내용으로 구성하거나, 적절한 길이의 자연스러운 텍스트로 대체하는 것이 좋겠습니다.

-    '정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.정말 인상 깊게 읽은 책이에요.',
+    '정말 인상 깊게 읽은 책이에요. 한강 작가의 섬세한 심리 묘사와 독특한 서사 구조가 인상적이었습니다. 특히 주인공의 내적 갈등과 변화 과정이 매우 사실적으로 그려져 있어서 몰입도가 높았어요. 현대 사회의 억압적인 구조와 개인의 자유 의지에 대한 깊이 있는 성찰을 담고 있는 작품이라고 생각합니다.',

23-69: 주석된 댓글 데이터를 활성화하는 것을 고려해보세요.

현재 빈 배열로 되어 있어 EmptyState만 표시됩니다. 개발 및 테스트 목적으로 주석된 데이터를 활성화하는 것이 좋겠습니다.

주석을 해제하여 댓글 UI를 테스트할 수 있도록 하시겠습니까?

src/types/post.ts (1)

31-31: ID 타입 일관성을 검토해보세요.

commentIdnumber 타입이고 replyCommentIdnumber 타입인데, 다른 곳에서 postIdstring 타입을 사용하고 있습니다. ID 타입을 통일하는 것을 고려해보세요.

프로젝트 전체에서 ID 타입 일관성을 위해 모두 string 또는 number로 통일하는 것이 좋겠습니다.

Also applies to: 36-36

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

37-42: "답글작성" 기능이 아직 구현되지 않았습니다.

클릭 가능한 스타일이 적용되어 있지만 실제 기능이 없어 사용자에게 혼란을 줄 수 있습니다.

답글 작성 기능을 구현하거나 임시로 비활성화 상태로 표시하는 것을 권장합니다. 구현이 필요하시면 도움을 드릴 수 있습니다.


109-109: 하드코딩된 폰트 크기를 글로벌 스타일로 통일하세요.

10px이 하드코딩되어 있어 디자인 시스템의 일관성을 해칩니다.

-      font-size: 10px;
+      font-size: ${typography.fontSize.xs};

또는 더 작은 폰트 크기가 필요하다면 글로벌 스타일에 xxs 크기를 추가하는 것을 고려해보세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f87a3a and edfe786.

⛔ Files ignored due to path filters (3)
  • src/assets/feed/activeLike.svg is excluded by !**/*.svg
  • src/assets/feed/like.svg is excluded by !**/*.svg
  • src/assets/feed/replyIcon.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • src/components/common/Post/PostBody.tsx (1 hunks)
  • src/components/common/Post/PostFooter.tsx (1 hunks)
  • src/components/common/Post/Reply.tsx (1 hunks)
  • src/components/common/Post/ReplyList.tsx (1 hunks)
  • src/components/common/Post/SubReply.tsx (1 hunks)
  • src/components/common/TitleHeader.tsx (3 hunks)
  • src/components/feed/FeedPost.tsx (2 hunks)
  • src/components/feed/TotalBar.tsx (0 hunks)
  • src/components/today-words/MessageInput.tsx (2 hunks)
  • src/data/postData.ts (1 hunks)
  • src/pages/feed/FeedDetailPage.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/types/post.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/feed/TotalBar.tsx
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/components/common/Post/ReplyList.tsx (2)
src/types/post.ts (1)
  • ReplyListProps (55-57)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/pages/feed/FeedDetailPage.tsx (3)
src/components/common/Wrapper.tsx (1)
  • Wrapper (3-14)
src/data/postData.ts (2)
  • mockFeedPost (5-20)
  • mockCommentList (23-69)
src/components/today-words/MessageInput.styled.ts (1)
  • MessageInput (27-53)
src/data/postData.ts (1)
src/types/post.ts (2)
  • FeedPostProps (24-27)
  • ReplyDataProps (42-52)
src/components/common/Post/SubReply.tsx (2)
src/types/post.ts (1)
  • SubReplyDataProps (30-39)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/components/common/Post/Reply.tsx (2)
src/types/post.ts (1)
  • ReplyDataProps (42-52)
src/styles/global/global.ts (2)
  • colors (4-53)
  • typography (56-76)
src/components/today-words/MessageInput.tsx (1)
src/components/today-words/MessageInput.styled.ts (3)
  • MessageInput (27-53)
  • InputContainer (4-15)
  • MessageInputWrapper (17-25)
🔇 Additional comments (21)
src/components/common/Post/PostFooter.tsx (1)

4-9: Import 경로 변경이 올바르게 적용되었습니다.

컴포넌트가 common/Post/ 디렉토리로 이동됨에 따라 asset import 경로가 적절히 업데이트되었습니다.

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

3-3: BookInfoCard import 경로가 적절히 업데이트되었습니다.

컴포넌트 구조 변경에 따라 BookInfoCard를 feed 디렉토리에서 가져오도록 경로가 올바르게 수정되었습니다.

src/pages/index.tsx (3)

19-19: 새로운 FeedDetailPage 컴포넌트 import가 추가되었습니다.

피드 상세보기 페이지 구현을 위한 필수 import입니다.


35-35: 피드 상세보기를 위한 새로운 라우트가 추가되었습니다.

feed/:feedId 라우트는 RESTful 패턴을 따르며 피드 상세보기 기능을 위해 적절합니다.


37-37: 팔로워 목록 라우트 경로가 개선되었습니다.

feed/:type에서 follow/:type으로 변경하여 라우트 의미가 더 명확해지고 새로운 피드 상세 라우트와의 충돌을 방지합니다.

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

2-4: Post 컴포넌트들의 import 경로가 공통 디렉토리로 적절히 변경되었습니다.

컴포넌트를 common/Post/ 디렉토리로 이동시킨 리팩토링이 올바르게 반영되었습니다.


31-31: isMyFeed prop의 boolean 강제 변환이 추가되었습니다.

!!isMyFeed를 사용하여 optional 속성을 명시적으로 boolean으로 변환하는 것은 타입 안전성을 향상시킵니다.

src/components/today-words/MessageInput.tsx (1)

14-14: placeholder prop 추가로 재사용성이 향상되었습니다.

동적 placeholder 텍스트 지원으로 컴포넌트의 유연성이 개선되었습니다.

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

1-11: Import 구문들이 잘 구성되었습니다.

필요한 모듈들이 적절히 import되어 있고, 경로 별칭(@)을 일관성 있게 사용하고 있습니다.


20-25: 댓글 전송 로직이 잘 구현되었습니다.

빈 문자열 전송 방지 로직(message.trim() 체크)과 전송 후 입력 필드 초기화가 적절히 구현되어 있습니다.

src/components/common/Post/ReplyList.tsx (1)

7-29: 컴포넌트 로직이 잘 구현되었습니다.

조건부 렌더링과 중첩된 댓글 구조가 적절히 처리되어 있고, key prop도 올바르게 설정되어 있습니다.

src/data/postData.ts (1)

5-20: 모킹 데이터가 적절히 구성되었습니다.

타입 안전성이 보장되고 있고, 한국어 콘텐츠로 현지화가 잘 되어 있습니다.

src/components/common/TitleHeader.tsx (2)

53-53: rightIcon 타입 정의가 적절합니다.

React.ReactNode 타입을 사용하여 유연성을 제공하고 있습니다.


75-85: 조건부 렌더링 로직이 잘 구현되었습니다.

rightIcon이 rightButton보다 우선순위를 갖도록 설계되어 있고, 각각의 클릭 핸들러와 스타일링이 적절히 적용되어 있습니다.

src/types/post.ts (3)

21-21: isMyFeed 속성의 optional 변경이 적절합니다.

기존 코드와의 호환성을 유지하면서 더 유연한 사용을 가능하게 합니다.

Also applies to: 26-26


30-39: SubReplyDataProps 인터페이스가 잘 설계되었습니다.

대댓글에 필요한 모든 필드가 포함되어 있고, 타입이 명확히 정의되어 있습니다.


42-52: ReplyDataProps 인터페이스가 중첩 구조를 잘 지원합니다.

댓글과 대댓글의 관계가 명확히 정의되어 있고, 재귀적 구조를 적절히 표현하고 있습니다.

src/components/common/Post/SubReply.tsx (3)

1-8: 임포트 구조가 잘 정의되어 있습니다.

React hooks, 스타일링, 타입, 그리고 에셋들이 명확하게 구분되어 임포트되어 있어 좋습니다. 상대 경로 사용도 일관성 있게 적용되었습니다.


10-24: 좋아요 기능의 상태 관리가 올바르게 구현되었습니다.

useState를 사용한 로컬 상태 관리와 토글 로직이 정확합니다. 좋아요 상태에 따른 카운트 증감 로직도 적절합니다.


54-114: 스타일링이 일관성 있게 잘 구현되었습니다.

글로벌 스타일 시스템을 활용한 색상과 타이포그래피 사용이 적절하며, 플렉스 레이아웃도 올바르게 구성되어 있습니다.

src/components/common/Post/Reply.tsx (1)

1-7: 임포트 구조가 적절합니다.

필요한 모든 의존성이 명확하게 임포트되어 있으며, 타입 임포트도 올바르게 분리되어 있습니다.

Comment on lines 26 to 51
return (
<Container>
<ReplyIcon>
<img src={replyIcon} alt="대댓글" />
</ReplyIcon>
<Content>
<PostHeader
profileImgUrl={profileImgUrl}
userName={userName}
userTitle={userTitle}
titleColor={titleColor}
createdAt={createdAt}
/>
<ReplySection>
<div className="left">
<div className="reply">{subreplyContent}</div>
<div className="sub-reply">답글작성</div>
</div>
<div className="right">
<img src={liked ? activeLike : like} onClick={handleLike} />
<div className="count">{likeCount}</div>
</div>
</ReplySection>
</Content>
</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

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

좋아요 버튼에 접근성 속성이 누락되어 있습니다. 스크린 리더 사용자를 위해 개선이 필요합니다.

다음과 같이 수정하여 접근성을 개선하세요:

-            <img src={liked ? activeLike : like} onClick={handleLike} />
+            <img 
+              src={liked ? activeLike : like} 
+              onClick={handleLike}
+              alt={liked ? "좋아요 취소" : "좋아요"}
+              role="button"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === 'Enter' || e.key === ' ') {
+                  e.preventDefault();
+                  handleLike();
+                }
+              }}
+            />
📝 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
return (
<Container>
<ReplyIcon>
<img src={replyIcon} alt="대댓글" />
</ReplyIcon>
<Content>
<PostHeader
profileImgUrl={profileImgUrl}
userName={userName}
userTitle={userTitle}
titleColor={titleColor}
createdAt={createdAt}
/>
<ReplySection>
<div className="left">
<div className="reply">{subreplyContent}</div>
<div className="sub-reply">답글작성</div>
</div>
<div className="right">
<img src={liked ? activeLike : like} onClick={handleLike} />
<div className="count">{likeCount}</div>
</div>
</ReplySection>
</Content>
</Container>
);
return (
<Container>
<ReplyIcon>
<img src={replyIcon} alt="대댓글" />
</ReplyIcon>
<Content>
<PostHeader
profileImgUrl={profileImgUrl}
userName={userName}
userTitle={userTitle}
titleColor={titleColor}
createdAt={createdAt}
/>
<ReplySection>
<div className="left">
<div className="reply">{subreplyContent}</div>
<div className="sub-reply">답글작성</div>
</div>
<div className="right">
<img
src={liked ? activeLike : like}
onClick={handleLike}
alt={liked ? "좋아요 취소" : "좋아요"}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleLike();
}
}}
/>
<div className="count">{likeCount}</div>
</div>
</ReplySection>
</Content>
</Container>
);
🤖 Prompt for AI Agents
In src/components/common/Post/SubReply.tsx between lines 26 and 51, the like
button image lacks accessibility attributes. To fix this, replace the img
element used as a button with a semantic button element or add role="button" and
tabIndex="0" to make it keyboard accessible. Also, include an aria-label
describing the button's action, such as "좋아요 버튼" or "Like button," and ensure
the onClick handler is preserved for interaction.

Comment on lines +48 to +94
const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 12px;
`;

const ReplySection = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 20px;

.left {
display: flex;
flex-direction: column;
gap: 12px;

.reply {
color: ${colors.grey[100]};
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
line-height: 20px;
}
.sub-reply {
color: ${colors.grey[300]};
font-size: ${typography.fontSize.xs};
font-weight: ${typography.fontWeight.semibold};
line-height: normal;
cursor: pointer;
}
}

.right {
display: flex;
flex-direction: column;
cursor: pointer;

.count {
text-align: center;
color: ${colors.grey[100]};
font-size: 10px;
font-weight: ${typography.fontWeight.medium};
line-height: normal;
}
}
`;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스타일 코드가 SubReply와 거의 동일합니다.

두 컴포넌트의 스타일링이 중복되어 유지보수성이 떨어집니다.

공통 스타일을 별도 파일로 분리하거나, 더 나은 방법으로는 하나의 기본 컴포넌트를 만들고 props로 차이점을 처리하는 것을 권장합니다:

// BaseReply.tsx
interface BaseReplyProps {
  showReplyIcon?: boolean;
  // ... other props
}

const BaseReply = ({ showReplyIcon = false, ...props }: BaseReplyProps) => {
  // 공통 로직
  return (
    <Container>
      {showReplyIcon && <ReplyIcon>...</ReplyIcon>}
      {/* 공통 렌더링 로직 */}
    </Container>
  );
};
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx between lines 48 and 94, the styling
code for Reply and SubReply components is almost identical, causing duplication
and reducing maintainability. Refactor by extracting the shared styles into a
single base styled component or a separate file, then create one base Reply
component that accepts props to handle the differences in styling or behavior.
This approach centralizes common styles and logic, improving code reuse and ease
of updates.

Comment on lines 9 to 23
const Reply = ({
profileImgUrl,
userName,
userTitle,
titleColor,
createdAt,
initialLikeCount,
replyContent,
}: ReplyDataProps) => {
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState<number>(initialLikeCount);
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

Reply와 SubReply 컴포넌트 간 코드 중복이 발생하고 있습니다.

두 컴포넌트가 동일한 상태 관리 로직과 이벤트 핸들러를 가지고 있어 중복 코드가 많습니다.

공통 로직을 커스텀 훅으로 분리하는 것을 권장합니다:

// hooks/useLike.ts
export const useLike = (initialCount: number) => {
  const [liked, setLiked] = useState(false);
  const [likeCount, setLikeCount] = useState<number>(initialCount);
  
  const handleLike = () => {
    setLiked(!liked);
    setLikeCount(prev => (liked ? prev - 1 : prev + 1));
  };
  
  return { liked, likeCount, handleLike };
};
🤖 Prompt for AI Agents
In src/components/common/Post/Reply.tsx around lines 9 to 23, the like state
management and handler logic is duplicated between Reply and SubReply
components. To fix this, extract the like-related state and handleLike function
into a custom hook named useLike that accepts the initial like count and returns
liked, likeCount, and handleLike. Then replace the duplicated state and handler
in both components by using this custom hook to reduce code duplication and
improve maintainability.

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 (1)
src/components/feed/BookInfoCard.tsx (1)

52-52: ISBN 타입을 검토해 보세요.

ISBN은 일반적으로 문자열 형태(하이픈 포함)로 사용되는 경우가 많습니다. number 타입보다는 string 타입이 더 적절할 수 있습니다.

-  isbn: number;
+  isbn: string;

그리고 src/types/post.ts의 PostData 인터페이스도 동일하게 수정해야 합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cb00eb5 and 894ae35.

📒 Files selected for processing (10)
  • src/components/common/Post/PostBody.tsx (3 hunks)
  • src/components/common/Post/PostHeader.tsx (3 hunks)
  • src/components/common/Post/Reply.tsx (1 hunks)
  • src/components/common/Post/SubReply.tsx (1 hunks)
  • src/components/feed/BookInfoCard.tsx (2 hunks)
  • src/components/feed/Profile.tsx (4 hunks)
  • src/data/postData.ts (1 hunks)
  • src/pages/feed/Feed.tsx (1 hunks)
  • src/pages/feed/OtherFeedPage.tsx (1 hunks)
  • src/types/post.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/pages/feed/OtherFeedPage.tsx
  • src/pages/feed/Feed.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/common/Post/PostBody.tsx
  • src/components/common/Post/SubReply.tsx
  • src/components/common/Post/Reply.tsx
  • src/data/postData.ts
  • src/components/common/Post/PostHeader.tsx
🔇 Additional comments (7)
src/types/post.ts (3)

4-4: PostData 인터페이스 확장이 적절합니다.

userId와 isbn 속성 추가로 사용자 식별과 도서 네비게이션 기능을 지원할 수 있게 되었습니다.

Also applies to: 9-9


23-23: isMyFeed 속성을 optional로 변경한 것이 적절합니다.

컴포넌트의 재사용성을 높이고 다양한 컨텍스트에서 유연하게 사용할 수 있게 되었습니다.

Also applies to: 28-29


31-34: PostBodyProps 타입 정의가 효율적입니다.

Pick 유틸리티 타입을 사용하여 필요한 속성만 선택적으로 추출한 것이 좋은 설계입니다. 컴포넌트의 관심사를 명확히 분리했습니다.

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

1-1: 네비게이션 기능 구현이 잘 되었습니다.

useNavigate 훅을 사용한 라우팅 구현이 적절하고, ISBN을 통한 도서 상세 페이지 네비게이션이 직관적입니다.

Also applies to: 55-61, 63-63

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

1-1: 상태 관리 추가가 적절합니다.

내부 상태로 팔로우 여부를 관리하는 것이 컴포넌트의 독립성을 높입니다.

Also applies to: 19-19


44-46: 동적 텍스트 표시가 잘 구현되었습니다.

상태에 따른 버튼 텍스트 변경이 직관적이고 사용자 경험을 개선합니다.


115-115: 커서 스타일 추가가 적절합니다.

클릭 가능한 요소임을 명확히 표시하여 사용자 경험을 개선했습니다.

Comment on lines +21 to +29
const toggleFollow = () => {
if (followed) {
// await axios.delete(`/api/unfollow/${userName}`);
} else {
// await axios.post(`/api/follow/${userName}`);
}
setFollowed(prev => !prev);
console.log(`${userName} - ${followed ? '띱 취소' : '띱 요청'}`);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

toggleFollow 함수의 비동기 처리를 개선해 보세요.

현재 주석 처리된 API 호출 부분에서 몇 가지 개선점이 있습니다:

  1. 에러 처리 부족: API 호출 실패 시 상태 롤백이 필요합니다.
  2. 로딩 상태 관리: 요청 중 중복 클릭을 방지해야 합니다.
  3. 상태 업데이트 순서: API 응답 후 상태를 업데이트하는 것이 안전합니다.
const toggleFollow = async () => {
+  const prevFollowed = followed;
+  setFollowed(prev => !prev); // 낙관적 업데이트
+  
  try {
    if (followed) {
-      // await axios.delete(`/api/unfollow/${userName}`);
+      await axios.delete(`/api/unfollow/${userName}`);
    } else {
-      // await axios.post(`/api/follow/${userName}`);
+      await axios.post(`/api/follow/${userName}`);
    }
-    setFollowed(prev => !prev);
    console.log(`${userName} - ${followed ? '띱 취소' : '띱 요청'}`);
+  } catch (error) {
+    // 실패 시 상태 롤백
+    setFollowed(prevFollowed);
+    console.error('팔로우 상태 변경 실패:', error);
+  }
};
📝 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 toggleFollow = () => {
if (followed) {
// await axios.delete(`/api/unfollow/${userName}`);
} else {
// await axios.post(`/api/follow/${userName}`);
}
setFollowed(prev => !prev);
console.log(`${userName} - ${followed ? '띱 취소' : '띱 요청'}`);
};
const toggleFollow = async () => {
const prevFollowed = followed;
setFollowed(prev => !prev); // 낙관적 업데이트
try {
if (followed) {
await axios.delete(`/api/unfollow/${userName}`);
} else {
await axios.post(`/api/follow/${userName}`);
}
console.log(`${userName} - ${followed ? '띱 취소' : '띱 요청'}`);
} catch (error) {
// 실패 시 상태 롤백
setFollowed(prevFollowed);
console.error('팔로우 상태 변경 실패:', error);
}
};
🤖 Prompt for AI Agents
In src/components/feed/Profile.tsx lines 21 to 29, the toggleFollow function
lacks proper async handling for the API calls. To fix this, make toggleFollow an
async function, add try-catch blocks to handle errors and rollback the followed
state if the API call fails, introduce a loading state to prevent multiple
clicks during the request, and update the followed state only after a successful
API response.

Comment on lines +36 to 66
// 대댓글(SubReply)
export interface SubReplyDataProps {
replyCommentId: number;
profileImgUrl: string;
userName: string;
userId: number;
userTitle: string;
titleColor: string;
createdAt: string;
subreplyContent: string;
initialLikeCount: number;
}

// 댓글(Reply)
export interface ReplyDataProps {
commentId: number;
profileImgUrl: string;
userName: string;
userId: number;
userTitle: string;
titleColor: string;
createdAt: string;
replyContent: string;
initialLikeCount: number;
replyCommentList: SubReplyDataProps[];
}

// ReplyList Props
export interface ReplyListProps {
commentList: ReplyDataProps[];
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

댓글/대댓글 구조 설계를 개선해 보세요.

현재 구조에서 몇 가지 개선점이 있습니다:

  1. 일관성 부족: SubReplyDataProps와 ReplyDataProps에서 공통 속성들(profileImgUrl, userName, userId 등)이 중복됩니다.
  2. 네이밍 불일치: subreplyContentreplyContent의 명명 규칙이 일관되지 않습니다.
  3. 확장성 부족: 대댓글의 대댓글은 지원하지 않는 구조입니다.

다음과 같이 개선해 보세요:

+// 공통 사용자 정보 인터페이스
+export interface UserInfo {
+  profileImgUrl: string;
+  userName: string;
+  userId: number;
+  userTitle: string;
+  titleColor: string;
+}
+
+// 공통 댓글 속성
+export interface BaseComment {
+  id: number;
+  content: string;
+  createdAt: string;
+  initialLikeCount: number;
+  user: UserInfo;
+}
+
+// 대댓글
+export interface SubReplyDataProps extends BaseComment {
+  parentCommentId: number;
+}
+
+// 댓글
+export interface ReplyDataProps extends BaseComment {
+  replies: SubReplyDataProps[];
+}
-// 대댓글(SubReply)
-export interface SubReplyDataProps {
-  replyCommentId: number;
-  profileImgUrl: string;
-  userName: string;
-  userId: number;
-  userTitle: string;
-  titleColor: string;
-  createdAt: string;
-  subreplyContent: string;
-  initialLikeCount: number;
-}
-
-// 댓글(Reply)
-export interface ReplyDataProps {
-  commentId: number;
-  profileImgUrl: string;
-  userName: string;
-  userId: number;
-  userTitle: string;
-  titleColor: string;
-  createdAt: string;
-  replyContent: string;
-  initialLikeCount: number;
-  replyCommentList: SubReplyDataProps[];
-}
📝 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
// 대댓글(SubReply)
export interface SubReplyDataProps {
replyCommentId: number;
profileImgUrl: string;
userName: string;
userId: number;
userTitle: string;
titleColor: string;
createdAt: string;
subreplyContent: string;
initialLikeCount: number;
}
// 댓글(Reply)
export interface ReplyDataProps {
commentId: number;
profileImgUrl: string;
userName: string;
userId: number;
userTitle: string;
titleColor: string;
createdAt: string;
replyContent: string;
initialLikeCount: number;
replyCommentList: SubReplyDataProps[];
}
// ReplyList Props
export interface ReplyListProps {
commentList: ReplyDataProps[];
}
// 공통 사용자 정보 인터페이스
export interface UserInfo {
profileImgUrl: string;
userName: string;
userId: number;
userTitle: string;
titleColor: string;
}
// 공통 댓글 속성
export interface BaseComment {
id: number;
content: string;
createdAt: string;
initialLikeCount: number;
user: UserInfo;
}
// 대댓글
export interface SubReplyDataProps extends BaseComment {
parentCommentId: number;
}
// 댓글
export interface ReplyDataProps extends BaseComment {
replies: SubReplyDataProps[];
}
// ReplyList Props
export interface ReplyListProps {
commentList: ReplyDataProps[];
}
🤖 Prompt for AI Agents
In src/types/post.ts between lines 36 and 66, the current ReplyDataProps and
SubReplyDataProps interfaces have duplicated common fields, inconsistent naming
for content properties, and do not support nested replies beyond one level.
Refactor by creating a base interface for shared properties, unify the content
property name across all reply levels, and modify the structure to allow
recursive nesting of replies to support replies of replies. This will improve
consistency, reduce duplication, and enhance extensibility.

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 (1)
src/components/common/ScrollToTop.tsx (1)

3-13: 주석 처리된 코드 정리 및 사용자 경험 개선을 고려해보세요.

컴포넌트의 핵심 기능은 올바르게 구현되었습니다. 다음 개선사항을 제안합니다:

  1. 주석 처리된 코드를 제거하거나 실제로 사용할지 결정해주세요.
  2. 부드러운 스크롤 효과를 위해 behavior: 'smooth' 옵션을 고려해보세요.

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

  useEffect(() => {
    window.scrollTo({
      top: 0,
-     //   left: 0,
-     //   behavior: 'smooth',
+     behavior: 'smooth',
    });
  }, []);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 894ae35 and 0ddc576.

📒 Files selected for processing (2)
  • src/components/common/ScrollToTop.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/index.tsx
🔇 Additional comments (2)
src/components/common/ScrollToTop.tsx (2)

1-2: Import문이 정확합니다.

React의 useEffect 훅을 올바르게 import하고 있습니다.


15-15: Export 구문이 적절합니다.

표준적인 React 컴포넌트 default export 패턴을 따르고 있습니다.

@heeeeyong heeeeyong changed the title feat: 피드 글 상세보기 페이지 구현 feat: 피드 글 상세보기 페이지, 사용자 검색 페이지 구현 Jul 21, 2025
@heeeeyong heeeeyong changed the title feat: 피드 글 상세보기 페이지, 사용자 검색 페이지 구현 feat: 피드 글 상세보기 페이지 Jul 21, 2025
@heeeeyong heeeeyong merged commit 7ec2d34 into develop Jul 21, 2025
3 checks passed
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] 피드 게시글 상세보기 페이지

1 participant