Skip to content

feat: 새 글 작성 페이지 구현#56

Merged
ljh130334 merged 12 commits into
developfrom
feat/newwriting
Jul 24, 2025
Merged

feat: 새 글 작성 페이지 구현#56
ljh130334 merged 12 commits into
developfrom
feat/newwriting

Conversation

@ljh130334
Copy link
Copy Markdown
Member

@ljh130334 ljh130334 commented Jul 22, 2025

#️⃣연관된 이슈

#42

📝작업 내용

주요 구현 기능

1️⃣ 책 선택 기능
기존 BookSelectionSection 컴포넌트를 재사용하여 BookSearchBottomSheet를 통한 책 검색 및 선택 기능을 구현했습니다. 사용자는 "저장한 책"과 "모임 책" 필터를 통해 원하는 책을 쉽게 찾을 수 있으며, 검색어 입력 시에는 선택된 필터 내에서만 검색이 이루어지도록 리팩토링 했습니다.

2️⃣ 글 작성 영역
PostContentSection 컴포넌트를 새로 개발하여 2000자 제한의 텍스트 입력이 가능합니다. 실시간 글자 수 카운터를 포함하고 있습니다.

3️⃣ 사진 첨부 기능
PhotoSection 컴포넌트를 통해 최대 3개까지 사진을 업로드할 수 있는 기능을 구현했습니다. 업로드된 사진은 미리보기와 함께 표시되며, 각 사진 우상단에 위치한 close.svg 아이콘을 통해 개별 삭제가 가능합니다.

4️⃣ 공개 설정
PrivacyToggleSection 컴포넌트를 새로 개발하여 글의 공개/비공개 설정을 간단한 토글 스위치로 제어할 수 있도록 했습니다. 모임 생성과 달리 비밀번호 없이 단순히 전체 피드 노출 여부만 설정할 수 있어 사용자 경험을 단순화했습니다.

5️⃣ 2단계 태그 시스템
TagSelectionSection 컴포넌트에서 가장 복잡한 기능을 구현했습니다. 상위 장르(문학, 과학·IT, 사회과학, 인문학, 예술) 선택 시 해당하는 하위 태그들이 표시되며, 사용자는 장르 상관없이 최대 5개까지 태그를 선택할 수 있습니다. 5개 선택 시 나머지 태그들은 자동으로 비활성화되어 6개 이상 선택을 방지합니다.

기술적 구현 사항

컴포넌트 재사용성
기존 프로젝트의 BookSelectionSection, Section, TitleHeader 등의 컴포넌트를 최대한 재사용하여 코드 중복을 줄이고 일관성 있는 UI를 구현했습니다. 특히 모임 생성과 글 작성의 차이점을 고려하여 필요한 부분만 새로운 컴포넌트로 분리했습니다.

상태 관리
React useState를 활용하여 각 섹션별 상태를 효율적으로 관리했습니다. 책 선택, 글 내용, 사진 파일 배열, 공개 설정, 선택된 태그 배열 등의 상태를 독립적으로 관리하면서도 전체적인 폼 유효성 검증을 구현했습니다.

사용자 경험 최적화
필수 항목(책 선택, 글 내용)이 모두 입력된 경우에만 완료 버튼이 활성화되도록 하여 사용자가 명확하게 작성 진행 상황을 파악할 수 있도록 했습니다. 각 섹션별로 적절한 안내 텍스트와 카운터를 제공하여 사용성을 높였습니다.

스크린샷 (선택)

비디오 올리려고 했는데 용량 이슈..

💬리뷰 요구사항(선택)

사진 업로드 최적화 - 현재 File 객체를 그대로 상태로 관리하고 있는데, 메모리 사용량이나 성능 측면에서 개선할 부분이 있는지 확인해주시면 좋겠습니다.

Summary by CodeRabbit

  • 신규 기능

    • 게시글 작성 페이지가 추가되었습니다. 책 선택, 글 작성, 사진 업로드(최대 3장), 공개/비공개 설정, 태그 선택(최대 5개)이 가능합니다.
    • 책 검색 및 선택을 위한 하단 시트 UI가 도입되었습니다.
    • 사진 업로드 섹션에서 사진 추가 및 삭제, 업로드된 사진 수 표시 기능이 제공됩니다.
    • 태그 선택 섹션에서 장르별 태그 선택 및 선택한 태그 관리가 가능합니다.
    • 게시글 내용 입력 시 글자 수 카운트 및 제한(최대 2000자)이 표시됩니다.
    • 공개/비공개 토글 UI가 추가되었습니다.
  • 스타일

    • 게시글 작성 관련 각 섹션(사진, 내용, 태그, 공개 설정)에 맞는 새로운 스타일이 적용되었습니다.
  • 버그 수정

    • 책 검색 시 탭별 필터링 및 검색 결과 동기화가 개선되었습니다.
  • 라우팅

    • 게시글 작성 페이지로 이동할 수 있는 경로가 추가되었습니다.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jul 22, 2025

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

Name Status Preview Comments Updated (UTC)
thip ❌ Failed (Inspect) Jul 24, 2025 2:46am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 22, 2025

"""

Walkthrough

이 변경사항은 게시글 생성 기능을 도입하며, 게시글 작성 페이지와 관련된 여러 UI 컴포넌트(책 선택, 본문 입력, 사진 업로드, 공개 범위 토글, 태그 선택 등)와 스타일 파일을 추가합니다. 또한, 책 검색 바텀시트의 탭별 필터링 로직을 리팩토링하고, 라우터에 게시글 작성 경로를 등록합니다.

Changes

파일(들) 변경 요약
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx 책 데이터 분리 및 탭별 필터링 로직 리팩토링, 탭 UI 조건부 렌더링, 핸들러 개선
src/components/createpost/PhotoSection.styled.ts
src/components/createpost/PostContentSection.styled.ts
src/components/createpost/PrivacyToggleSection.styled.ts
src/components/createpost/TagSelectionSection.styled.ts
게시글 생성 관련 각 UI 섹션의 스타일 컴포넌트 신규 추가
src/components/createpost/PhotoSection.tsx
src/components/createpost/PostContentSection.tsx
src/components/createpost/PrivacyToggleSection.tsx
src/components/createpost/TagSelectionSection.tsx
사진 업로드, 본문 입력, 공개/비공개 토글, 태그 선택 등 게시글 생성용 신규 리액트 컴포넌트 추가
src/pages/index.tsx 게시글 생성 페이지 라우트(post/create) 추가 및 컴포넌트 임포트
src/pages/post/CreatePost.styled.ts 게시글 생성 페이지 컨테이너 스타일 컴포넌트 추가
src/pages/post/CreatePost.tsx 게시글 생성 메인 컴포넌트 신규 추가(상태 관리, 섹션 조립, 이벤트 핸들러 등)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CreatePostPage
    participant BookSearchBottomSheet
    participant PhotoSection
    participant TagSelectionSection
    participant PrivacyToggleSection

    User->>CreatePostPage: 접속/이동
    User->>CreatePostPage: 책 선택 버튼 클릭
    CreatePostPage->>BookSearchBottomSheet: 바텀시트 오픈
    User->>BookSearchBottomSheet: 책 검색/탭 전환/선택
    BookSearchBottomSheet->>CreatePostPage: 책 선택 결과 전달
    User->>PhotoSection: 사진 추가/삭제
    User->>TagSelectionSection: 태그 선택/해제
    User->>PrivacyToggleSection: 공개/비공개 토글
    User->>CreatePostPage: 본문 입력
    User->>CreatePostPage: 완료 버튼 클릭
    CreatePostPage->>CreatePostPage: 유효성 검사 및 데이터 수집
    CreatePostPage->>User: 완료 처리(예: 네비게이션)
Loading

Estimated code review effort

3 (~50분)

Possibly related PRs

Suggested reviewers

  • heeeeyong

Poem

🐰
새 게시글, 토글, 사진, 태그
토끼가 뛰노는 UI의 새싹
책도 고르고, 비밀도 정해
사진 세 장, 태그 다섯까지
코드를 살포시 깡총!
리뷰어님, 이 변화와 함께
봄바람처럼 가벼운 마음으로 hop hop!

"""


📜 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 4776544 and 11a035a.

📒 Files selected for processing (1)
  • src/pages/post/CreatePost.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/post/CreatePost.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (1)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)

89-165: 필터링 로직의 중복을 제거하세요.

useEffecthandleTabChange에서 동일한 필터링 로직이 반복됩니다. 공통 함수로 추출하여 중복을 제거하세요.

+const filterBooks = (books: Book[], query: string) => {
+  if (query.trim() === '') {
+    return books;
+  }
+  return books.filter(
+    book =>
+      book.title.toLowerCase().includes(query.toLowerCase()) ||
+      book.author.toLowerCase().includes(query.toLowerCase()),
+  );
+};

useEffect(() => {
  const currentTabBooks = activeTab === 'saved' ? mockSavedBooks : mockGroupBooks;
- if (searchQuery.trim() === '') {
-   setFilteredBooks(currentTabBooks);
- } else {
-   const filtered = currentTabBooks.filter(
-     book =>
-       book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
-       book.author.toLowerCase().includes(searchQuery.toLowerCase()),
-   );
-   setFilteredBooks(filtered);
- }
+ setFilteredBooks(filterBooks(currentTabBooks, searchQuery));
}, [searchQuery, activeTab]);

const handleTabChange = (tab: TabType) => {
  setActiveTab(tab);
  const newTabBooks = tab === 'saved' ? mockSavedBooks : mockGroupBooks;
- if (searchQuery.trim() === '') {
-   setFilteredBooks(newTabBooks);
- } else {
-   const filtered = newTabBooks.filter(
-     book =>
-       book.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
-       book.author.toLowerCase().includes(searchQuery.toLowerCase()),
-   );
-   setFilteredBooks(filtered);
- }
+ setFilteredBooks(filterBooks(newTabBooks, searchQuery));
};
🧹 Nitpick comments (6)
src/components/createpost/PhotoSection.styled.ts (1)

2-2: 색상 사용의 일관성을 개선해주세요.

colorssemanticColors 두 가지를 모두 import하여 혼용하고 있습니다. 일관성을 위해 가능한 한 semanticColors를 우선적으로 사용하는 것을 권장합니다.

예시 수정:

-  border: 1px solid ${colors.grey[300]};
+  border: 1px solid ${semanticColors.text.ghost};
-  &:disabled {
-    background-color: ${colors.darkgrey.dark};
-    border: 1px solid ${colors.darkgrey.main};
+  &:disabled {
+    background-color: ${semanticColors.background.cardDark};
+    border: 1px solid ${semanticColors.background.card};
src/components/createpost/PostContentSection.tsx (1)

10-10: 선택적 개선: maxLength를 props로 받도록 고려

현재 maxLength가 하드코딩되어 있어도 문제없지만, 컴포넌트의 재사용성을 높이려면 props로 받는 것을 고려해볼 수 있습니다.

interface PostContentSectionProps {
  content: string;
  onContentChange: (value: string) => void;
+ maxLength?: number;
}

-const PostContentSection = ({ content, onContentChange }: PostContentSectionProps) => {
-  const maxLength = 2000;
+const PostContentSection = ({ content, onContentChange, maxLength = 2000 }: PostContentSectionProps) => {
src/pages/post/CreatePost.tsx (1)

63-69: 사진 관리 로직이 적절합니다.

File 객체를 직접 상태로 관리하는 현재 방식은 최대 3개 제한 하에서는 메모리 사용량 측면에서 큰 문제가 없습니다. 더 큰 규모의 업로드가 필요하다면 File의 메타데이터만 저장하고 실제 업로드 시에만 File 객체를 사용하는 방식을 고려할 수 있습니다.

src/components/createpost/PhotoSection.tsx (1)

52-52: 스타일 일관성을 위해 스타일드 컴포넌트 사용을 권장합니다.

인라인 스타일 대신 스타일드 컴포넌트를 사용하여 일관성을 유지하세요.

PhotoSection.styled.ts에 추가:

export const PhotoItem = styled.div`
  position: relative;
  width: 80px;
  height: 80px;
`;

그리고 컴포넌트에서:

+import { ..., PhotoItem } from './PhotoSection.styled';

-<div key={index} style={{ position: 'relative', width: '80px', height: '80px' }}>
+<PhotoItem key={index}>
  <PhotoImage src={createImageUrl(photo)} alt={`선택된 사진 ${index + 1}`} />
  <RemoveButton onClick={() => onPhotoRemove(index)}>
    <img src={closeIcon} alt="삭제" />
  </RemoveButton>
-</div>
+</PhotoItem>
src/components/createpost/TagSelectionSection.tsx (2)

40-51: 태그 선택 로직을 단순화할 수 있습니다.

handleTagToggle 함수에서 조건문을 중첩하여 복잡도가 높아졌습니다. 로직을 단순화하면 가독성과 유지보수성이 향상됩니다.

const handleTagToggle = (tag: string) => {
-  // 이미 선택된 태그면 해제
-  if (selectedTags.includes(tag)) {
-    onTagToggle(tag);
-    return;
-  }
-
-  // 5개 미만이면 추가 가능
-  if (selectedTags.length < 5) {
-    onTagToggle(tag);
-  }
+  // 이미 선택된 태그이거나 5개 미만일 때만 토글 허용
+  if (selectedTags.includes(tag) || selectedTags.length < 5) {
+    onTagToggle(tag);
+  }
};

101-101: 접근성을 개선하기 위해 적절한 alt 텍스트를 제공하세요.

현재 alt 텍스트가 너무 일반적입니다. 더 구체적인 설명을 제공하면 스크린 리더 사용자의 경험이 향상됩니다.

-<img src={closeIcon} alt="삭제" />
+<img src={closeIcon} alt={`${tag} 태그 삭제`} />
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8b95f51 and 50e030e.

⛔ Files ignored due to path filters (3)
  • src/assets/post/close.svg is excluded by !**/*.svg
  • src/assets/post/plus-disabled.svg is excluded by !**/*.svg
  • src/assets/post/plus.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (4 hunks)
  • src/components/createpost/PhotoSection.styled.ts (1 hunks)
  • src/components/createpost/PhotoSection.tsx (1 hunks)
  • src/components/createpost/PostContentSection.styled.ts (1 hunks)
  • src/components/createpost/PostContentSection.tsx (1 hunks)
  • src/components/createpost/PrivacyToggleSection.styled.ts (1 hunks)
  • src/components/createpost/PrivacyToggleSection.tsx (1 hunks)
  • src/components/createpost/TagSelectionSection.styled.ts (1 hunks)
  • src/components/createpost/TagSelectionSection.tsx (1 hunks)
  • src/pages/index.tsx (2 hunks)
  • src/pages/post/CreatePost.styled.ts (1 hunks)
  • src/pages/post/CreatePost.tsx (1 hunks)
🧬 Code Graph Analysis (11)
src/components/createpost/PrivacyToggleSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PrivacyToggleSection.styled.ts (2)
  • ToggleContainer (4-8)
  • ToggleLabel (10-14)
src/components/createpost/PrivacyToggleSection.styled.ts (1)
src/styles/global/global.ts (2)
  • semanticColors (80-153)
  • typography (56-77)
src/pages/post/CreatePost.tsx (1)
src/pages/post/CreatePost.styled.ts (1)
  • Container (4-14)
src/pages/post/CreatePost.styled.ts (1)
src/styles/global/global.ts (1)
  • semanticColors (80-153)
src/components/createpost/PostContentSection.styled.ts (1)
src/styles/global/global.ts (2)
  • semanticColors (80-153)
  • typography (56-77)
src/components/createpost/PhotoSection.styled.ts (1)
src/styles/global/global.ts (3)
  • colors (4-53)
  • semanticColors (80-153)
  • typography (56-77)
src/components/createpost/PostContentSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PostContentSection.styled.ts (3)
  • TextAreaBox (4-8)
  • TextArea (10-25)
  • CharacterCount (27-33)
src/components/createpost/TagSelectionSection.styled.ts (1)
src/styles/global/global.ts (3)
  • semanticColors (80-153)
  • colors (4-53)
  • typography (56-77)
src/components/createpost/TagSelectionSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/TagSelectionSection.styled.ts (10)
  • TagContainer (4-8)
  • GenreButtonGroup (10-14)
  • GenreButton (16-32)
  • SubTagGrid (34-38)
  • SubTagButton (40-60)
  • TagCount (62-67)
  • SelectedTagsSection (69-73)
  • SelectedTagsTitle (75-79)
  • SelectedTagList (81-85)
  • SelectedTagItem (87-109)
src/components/createpost/PhotoSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PhotoSection.styled.ts (6)
  • PhotoContainer (4-8)
  • PhotoGrid (10-14)
  • AddPhotoButton (16-41)
  • PhotoImage (49-54)
  • RemoveButton (56-78)
  • PhotoCount (80-85)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (2)
  • TabContainer (104-109)
  • Tab (111-134)
🪛 ESLint
src/pages/post/CreatePost.tsx

[error] 16-16: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 59-59: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🧰 Additional context used
🧬 Code Graph Analysis (11)
src/components/createpost/PrivacyToggleSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PrivacyToggleSection.styled.ts (2)
  • ToggleContainer (4-8)
  • ToggleLabel (10-14)
src/components/createpost/PrivacyToggleSection.styled.ts (1)
src/styles/global/global.ts (2)
  • semanticColors (80-153)
  • typography (56-77)
src/pages/post/CreatePost.tsx (1)
src/pages/post/CreatePost.styled.ts (1)
  • Container (4-14)
src/pages/post/CreatePost.styled.ts (1)
src/styles/global/global.ts (1)
  • semanticColors (80-153)
src/components/createpost/PostContentSection.styled.ts (1)
src/styles/global/global.ts (2)
  • semanticColors (80-153)
  • typography (56-77)
src/components/createpost/PhotoSection.styled.ts (1)
src/styles/global/global.ts (3)
  • colors (4-53)
  • semanticColors (80-153)
  • typography (56-77)
src/components/createpost/PostContentSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PostContentSection.styled.ts (3)
  • TextAreaBox (4-8)
  • TextArea (10-25)
  • CharacterCount (27-33)
src/components/createpost/TagSelectionSection.styled.ts (1)
src/styles/global/global.ts (3)
  • semanticColors (80-153)
  • colors (4-53)
  • typography (56-77)
src/components/createpost/TagSelectionSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/TagSelectionSection.styled.ts (10)
  • TagContainer (4-8)
  • GenreButtonGroup (10-14)
  • GenreButton (16-32)
  • SubTagGrid (34-38)
  • SubTagButton (40-60)
  • TagCount (62-67)
  • SelectedTagsSection (69-73)
  • SelectedTagsTitle (75-79)
  • SelectedTagList (81-85)
  • SelectedTagItem (87-109)
src/components/createpost/PhotoSection.tsx (2)
src/pages/group/CommonSection.styled.ts (1)
  • SectionTitle (13-18)
src/components/createpost/PhotoSection.styled.ts (6)
  • PhotoContainer (4-8)
  • PhotoGrid (10-14)
  • AddPhotoButton (16-41)
  • PhotoImage (49-54)
  • RemoveButton (56-78)
  • PhotoCount (80-85)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (2)
  • TabContainer (104-109)
  • Tab (111-134)
🪛 ESLint
src/pages/post/CreatePost.tsx

[error] 16-16: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 59-59: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🔇 Additional comments (15)
src/pages/post/CreatePost.styled.ts (1)

1-14: 코드 구조와 스타일링이 잘 구성되었습니다.

반응형 디자인을 고려한 컨테이너 스타일링이 적절하며, 전역 스타일 시스템을 올바르게 활용하고 있습니다. box-sizing과 flexbox 레이아웃 설정도 적절합니다.

src/components/createpost/PostContentSection.styled.ts (1)

1-33: 텍스트 입력 영역 스타일링이 일관성 있게 구현되었습니다.

전역 테마 시스템을 올바르게 활용하고, textarea의 기본 스타일을 적절히 제거했습니다. 문자 수 카운터의 위치와 스타일링도 적절합니다.

src/components/createpost/PrivacyToggleSection.styled.ts (1)

1-36: 토글 스위치 컴포넌트가 잘 구현되었습니다.

상태 기반 스타일링과 부드러운 전환 애니메이션이 적절히 적용되었으며, 타입스크립트 props를 올바르게 활용하고 있습니다. 테마 시스템 사용도 일관성이 있습니다.

src/components/createpost/PhotoSection.styled.ts (2)

16-41: 사진 추가 버튼의 상호작용 스타일링이 잘 구현되었습니다.

hover 및 disabled 상태에 대한 적절한 스타일링이 적용되어 사용자 경험이 향상될 것입니다.


56-78: 제거 버튼의 위치와 스타일링이 적절합니다.

절대 위치 지정과 반투명 배경을 통해 사용자가 쉽게 인식할 수 있도록 구현되었습니다.

src/pages/index.tsx (2)

13-13: 새로운 CreatePost 컴포넌트 import가 올바르게 추가되었습니다.


39-39: post/create 라우트가 적절히 추가되었습니다.

새로운 게시글 생성 기능을 위한 라우트가 기존 구조와 일관성 있게 추가되었습니다.

src/components/createpost/PrivacyToggleSection.tsx (1)

1-28: 컴포넌트 구현이 깔끔합니다.

단일 책임 원칙을 잘 따르고 있으며, 타입 안전성과 재사용성을 모두 확보한 좋은 구현입니다. 기존 공통 컴포넌트를 재사용하여 코드 중복을 방지한 점도 좋습니다.

src/components/createpost/PostContentSection.tsx (1)

1-31: 구현이 잘 되어 있습니다.

텍스트 입력과 실시간 글자 수 표시 기능이 잘 구현되어 있습니다. 사용자 경험을 고려한 placeholder와 적절한 rows 설정도 좋습니다.

src/pages/post/CreatePost.tsx (1)

82-127: 전체 구조가 잘 설계되었습니다.

각 섹션이 독립적인 컴포넌트로 분리되어 있어 유지보수성이 좋고, 공통 컴포넌트의 재사용으로 코드 중복을 방지했습니다. 폼 검증 로직과 완료 버튼 활성화 조건도 적절합니다.

src/components/createpost/PhotoSection.tsx (1)

21-74: 사진 업로드 기능이 잘 구현되었습니다.

파일 업로드 UX가 좋고, 최대 3개 제한과 미리보기 기능이 적절히 구현되어 있습니다. 접근성과 사용성도 잘 고려되었습니다.

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

40-80: 탭 기능을 위한 데이터 분리가 적절합니다.

mockSavedBooksmockGroupBooks로 분리하여 각 탭별로 다른 책 목록을 제공할 수 있도록 구조를 개선했습니다.


194-204: 조건부 탭 렌더링이 좋은 UX 개선입니다.

검색어가 있을 때 탭을 숨기는 것은 사용자가 현재 상태를 명확히 이해할 수 있도록 돕는 좋은 결정입니다.

src/components/createpost/TagSelectionSection.styled.ts (1)

1-109: 잘 구조화된 스타일 컴포넌트 파일입니다.

전역 디자인 시스템을 일관성 있게 활용하고, 각 컴포넌트의 역할이 명확하게 분리되어 있습니다. 상태별 스타일링(active, disabled, hover)도 적절히 구현되었습니다.

src/components/createpost/TagSelectionSection.tsx (1)

82-82: 태그 비활성화 로직이 직관적입니다.

선택되지 않은 태그가 5개 제한에 도달했을 때만 비활성화하는 로직이 매우 적절합니다. 이미 선택된 태그는 항상 해제 가능하도록 구현된 점이 좋습니다.

Comment on lines +37 to +39
const createImageUrl = (file: File) => {
return URL.createObjectURL(file);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메모리 누수 방지를 위해 URL 해제가 필요합니다.

URL.createObjectURL로 생성된 blob URL은 수동으로 해제해야 메모리 누수를 방지할 수 있습니다.

+import { useRef, useEffect } from 'react';

const PhotoSection = ({ photos, onPhotoAdd, onPhotoRemove }: PhotoSectionProps) => {
  const fileInputRef = useRef<HTMLInputElement>(null);
+ const urlsRef = useRef<string[]>([]);

+ useEffect(() => {
+   // 컴포넌트 언마운트 시 모든 URL 해제
+   return () => {
+     urlsRef.current.forEach(url => URL.revokeObjectURL(url));
+   };
+ }, []);

+ useEffect(() => {
+   // 사진 배열이 변경될 때마다 URL 업데이트
+   urlsRef.current.forEach(url => URL.revokeObjectURL(url));
+   urlsRef.current = photos.map(photo => URL.createObjectURL(photo));
+ }, [photos]);

  const createImageUrl = (file: File) => {
-   return URL.createObjectURL(file);
+   const index = photos.indexOf(file);
+   return urlsRef.current[index] || URL.createObjectURL(file);
  };

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

🤖 Prompt for AI Agents
In src/components/createpost/PhotoSection.tsx around lines 37 to 39, the
function createImageUrl uses URL.createObjectURL to generate a blob URL but does
not release it, which can cause memory leaks. Modify the code to call
URL.revokeObjectURL on the created URL when it is no longer needed, such as
during component cleanup or when the image is removed, to properly release the
allocated memory.

Comment on lines +23 to +29
const genreTagsMap: Record<string, string[]> = {
문학: ['소설', '시', '에세이', '인문학', '철학'],
'과학·IT': ['기술', '과학', 'AI', '데이터'],
사회과학: ['정치', '경제', '사회학', '심리학', '역사'],
인문학: ['철학', '역사', '문화', '언어학', '종교'],
예술: ['미술', '음악', '영화', '디자인', '사진'],
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

장르별 태그 매핑에 중복된 태그가 있습니다.

genreTagsMap에서 '철학'과 '역사' 태그가 여러 장르에 중복되어 있습니다:

  • '철학': 문학, 인문학
  • '역사': 사회과학, 인문학

이는 사용자 혼란을 야기하고 태그 선택 시 예상치 못한 동작을 일으킬 수 있습니다.

다음 중 하나의 방식으로 개선하는 것을 권장합니다:

const genreTagsMap: Record<string, string[]> = {
-  문학: ['소설', '시', '에세이', '인문학', '철학'],
+  문학: ['소설', '시', '에세이', '문학이론'],
  '과학·IT': ['기술', '과학', 'AI', '데이터'],
-  사회과학: ['정치', '경제', '사회학', '심리학', '역사'],
+  사회과학: ['정치', '경제', '사회학', '심리학'],
-  인문학: ['철학', '역사', '문화', '언어학', '종교'],
+  인문학: ['철학', '역사', '문화', '언어학', '종교'],
  예술: ['미술', '음악', '영화', '디자인', '사진'],
};
📝 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 genreTagsMap: Record<string, string[]> = {
문학: ['소설', '시', '에세이', '인문학', '철학'],
'과학·IT': ['기술', '과학', 'AI', '데이터'],
사회과학: ['정치', '경제', '사회학', '심리학', '역사'],
인문학: ['철학', '역사', '문화', '언어학', '종교'],
예술: ['미술', '음악', '영화', '디자인', '사진'],
};
const genreTagsMap: Record<string, string[]> = {
문학: ['소설', '시', '에세이', '문학이론'],
'과학·IT': ['기술', '과학', 'AI', '데이터'],
사회과학: ['정치', '경제', '사회학', '심리학'],
인문학: ['철학', '역사', '문화', '언어학', '종교'],
예술: ['미술', '음악', '영화', '디자인', '사진'],
};
🤖 Prompt for AI Agents
In src/components/createpost/TagSelectionSection.tsx around lines 23 to 29, the
genreTagsMap contains duplicate tags like '철학' and '역사' appearing in multiple
genres, which can confuse users and cause unexpected behavior. To fix this,
review the genre categories and assign each tag to only one appropriate genre,
removing duplicates to ensure tags are unique across genres. Alternatively,
consider restructuring the data model to allow tags to belong to multiple genres
explicitly if that fits the use case better.

Comment on lines +53 to +55
const handleSelectedTagRemove = (tag: string) => {
onTagToggle(tag);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복된 함수를 제거하세요.

handleSelectedTagRemove 함수는 handleTagToggle과 동일한 작업을 수행합니다. 불필요한 중복 함수입니다.

-const handleSelectedTagRemove = (tag: string) => {
-  onTagToggle(tag);
-};

// 그리고 JSX에서 직접 호출
-<SelectedTagItem key={tag} onClick={() => handleSelectedTagRemove(tag)}>
+<SelectedTagItem key={tag} onClick={() => onTagToggle(tag)}>
📝 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 handleSelectedTagRemove = (tag: string) => {
onTagToggle(tag);
};
// --- remove these lines entirely (they are no longer needed) ---
// const handleSelectedTagRemove = (tag: string) => {
// onTagToggle(tag);
// };
// -------------------------------------------------------------
// …later, in your JSX mapping selectedTags…
{selectedTags.map(tag => (
<SelectedTagItem
key={tag}
onClick={() => onTagToggle(tag)}
>
{tag}
</SelectedTagItem>
))}
🤖 Prompt for AI Agents
In src/components/createpost/TagSelectionSection.tsx around lines 53 to 55, the
function handleSelectedTagRemove duplicates the functionality of
handleTagToggle. Remove the handleSelectedTagRemove function entirely and
replace its usage with handleTagToggle to eliminate redundancy.

Comment thread src/pages/index.tsx Outdated
Comment thread src/pages/post/CreatePost.tsx Outdated
Comment thread src/pages/post/CreatePost.tsx Outdated
@ljh130334 ljh130334 changed the title Feat/newwriting feat: 새 글 작성 페이지 구현 Jul 23, 2025
@ljh130334 ljh130334 added ✨ Feature 기능 개발 🎨 Html&css 마크업 & 스타일링 labels Jul 23, 2025
@ljh130334 ljh130334 merged commit d2216c3 into develop Jul 24, 2025
2 of 3 checks passed
@ljh130334 ljh130334 deleted the feat/newwriting branch July 29, 2025 08:41
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