[network] #115 게시글 API 연동 - #116
Conversation
- 카테고리 enum<->한글 매핑 모듈 추가 (LEADERSHIP/COMMUNICATION/PLANNING 등 3개는 명세 확인, 나머지 9개는 추정치 - 백엔드 확인 필요)
- 게시글 목록(GET /api/posts): cursor 페이지네이션, sortBy/sortDirection/category/author/followingOnly/keyword 지원
- 게시글 상세(GET /api/posts/{postId}), 생성(POST /api/posts), 수정(PATCH, UI 미연결), 삭제(DELETE) 연동
- 좋아요 생성/삭제(POST·DELETE /api/posts/{postId}/likes)를 useLike에 연동, 실패 시 낙관적 업데이트 롤백
- 백엔드 API에 없는 모집상태(recruitStatus) 필터를 프론트에서 제거
- 게시글 카테고리 필터를 실제 카테고리 12종으로 교체
- 게시글 제목도 자소서와 동일하게 1~20자 제한 적용
- 게시글 상세 "본문 삭제" 메뉴를 삭제 API에 연결
- 이 브랜치는 fix/#93(PR #94) 위가 아니라 main 기준으로 새로 작성 — fix/#93의 mock 생성 로직을 대체하므로 병합 시 fix/#93은 정리 필요할 수 있음
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough게시글 카테고리 매핑과 응답 변환을 추가했다. 목록·상세 조회와 생성·수정·삭제·좋아요 API를 모크 모드와 함께 연결했다. 홈 화면 필터를 카테고리와 팔로잉 기준으로 변경했다. 작성 화면에 첨부파일 삭제와 업로드 제한을 추가했다. Changes게시글 API 연동
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant PostWritePage
participant useCreatePost
participant API
participant postMappers
PostWritePage->>useCreatePost: createPost 입력 전달
useCreatePost->>API: POST /api/posts
API-->>useCreatePost: 게시글 상세 응답
useCreatePost->>postMappers: mapPostDetail(data)
postMappers-->>useCreatePost: 프론트 게시글 반환
useCreatePost-->>PostWritePage: 생성 결과 또는 null 반환
sequenceDiagram
participant HomePage
participant usePosts
participant API
participant postMappers
HomePage->>usePosts: followingOnly, categories, author, keyword 전달
usePosts->>API: GET /api/posts with cursor
API-->>usePosts: posts, nextCursor, hasNext 반환
usePosts->>postMappers: mapPostSummary(posts)
postMappers-->>usePosts: 누적 게시글 목록 반환
usePosts-->>HomePage: 게시글 목록 반환
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
# Conflicts: # src/features/post/api/useCreatePost.js # src/mocks/mockPosts.js # src/pages/PostWritePage.jsx
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/features/post/api/postMappers.js (1)
16-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winauthor 매핑 로직 중복, 헬퍼로 뽑아내면 좋을 것 같아요.
mapPostSummary랑mapPostDetail에서author객체 만드는 부분이 완전히 똑같이 반복되고 있어요. 나중에nickname기본값 문구나 필드명이 바뀌면 두 군데 다 찾아서 고쳐야 하는데, 하나라도 놓치면 목록/상세 화면에서 작성자 표시가 서로 달라지는 버그로 이어질 수 있어요. 공통 헬퍼 하나로 묶어두면 이런 실수를 원천 차단할 수 있어요.♻️ 제안 diff
+function mapAuthor(rawAuthor) { + return { + id: rawAuthor?.memberId, + name: rawAuthor?.nickname ?? '알 수 없음', + profileImage: rawAuthor?.profileImage ?? '', + isFollowing: rawAuthor?.isFollowing ?? false, + }; +} + export function mapPostSummary(raw) { return { id: raw.postId, title: raw.title, content: raw.summary, - author: { - id: raw.author?.memberId, - name: raw.author?.nickname ?? '알 수 없음', - profileImage: raw.author?.profileImage ?? '', - isFollowing: raw.author?.isFollowing ?? false, - }, + author: mapAuthor(raw.author), createdAt: formatDateTime(raw.createdAt), ...
mapPostDetail도 동일하게mapAuthor(raw.author)로 교체하면 돼요.Also applies to: 38-43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/post/api/postMappers.js` around lines 16 - 21, Extract the duplicated author mapping from mapPostSummary and mapPostDetail into a shared mapAuthor helper, preserving the existing fallback values and mapped fields. Replace both inline author object constructions with mapAuthor(raw.author) so list and detail responses use the same logic.src/pages/PostWritePage.jsx (2)
20-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win제목 생성/카테고리 변환 로직,
features/post로 옮기면 페이지가 더 가벼워질 것 같아요.
buildTitle이랑 카테고리 id→코드 변환 로직이 페이지 컴포넌트 안에 직접 들어있는데, 둘 다 UI 상태에 의존하지 않는 순수 로직이라features/post쪽(유틸 함수나useCreatePost호출 전 전처리 훅 등)으로 빼기 쉬워 보여요. 페이지는 이벤트 핸들러 연결과 렌더링만 담당하도록 유지하면, 나중에 다른 페이지(예: 수정 화면)에서도 같은 로직을 재사용하기 편해질 것 같아요.지금 당장 급한 건 아니니 여유 될 때 리팩터링해보면 좋을 것 같아요!
As per path instructions, "페이지 컴포넌트는 얇게 유지하는 것이 이 프로젝트의 원칙입니다. 비즈니스 로직이나 복잡한 UI가 페이지에 직접 들어 있으면 features/ 하위 도메인 폴더로 분리하도록 제안해주세요."
Also applies to: 69-79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 20 - 26, Move the pure post-title generation logic in buildTitle and the category ID-to-code conversion logic around the referenced category handling into the features/post domain, using reusable utility functions or preprocessing associated with useCreatePost. Update PostWritePage to consume those abstractions so it only coordinates event handlers and rendering, while preserving the existing title and category conversion behavior.Source: Path instructions
15-16: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win카테고리 직렬화와 제목 생성은 페이지 밖으로 빼는 게 좋아요.
PostWritePage가buildTitle과selectedCategories -> code변환까지 직접 맡고 있어서,MOCK_CATEGORIES/POST_CATEGORIES처럼 같은 도메인 정보가 여러 곳에 흩어져요.features/post쪽 유틸이나 훅으로 옮기고 카테고리 소스도 한 군데로 통일하면 페이지는 단계 전환만 담당하게 되고, 목록/매핑 드리프트도 줄일 수 있어요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 15 - 16, Move the title-building logic and selectedCategories-to-code serialization out of PostWritePage into a reusable utility or hook under features/post. Consolidate category data usage by choosing one authoritative source instead of separately relying on MOCK_CATEGORIES and POST_CATEGORIES, then update PostWritePage to consume the shared helpers so it only manages step transitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/post/api/useCreatePost.js`:
- Around line 19-41: Update the USE_MOCK branch in the post creation flow to
construct mockPost with the same shape expected by mapPostDetail: add fileUrls,
isMine, updatedAt, and isUpdated, and remove the unsupported commentCount field.
Preserve the existing author, tags, privacy, and count values while ensuring
mock and real creation responses expose consistent fields.
In `@src/features/post/api/useLike.js`:
- Around line 29-54: Prevent overlapping like requests in toggleLike by adding
an isToggling state and ref-based lock, returning immediately when a toggle is
already in progress, and releasing the lock in all completion paths while
preserving rollback behavior on failure. Expose isToggling from the useLike hook
and update the consuming like button component to disable the button while a
toggle is in progress.
In `@src/features/post/api/useUpdatePost.js`:
- Around line 14-28: Update updatePost so categories is required rather than
silently defaulting to an empty array: validate that the caller provides an
array and reject the update before api.patch when it is missing or invalid.
Continue sending the validated categories array for valid requests, preserving
the existing update behavior for other fields.
In `@src/features/post/components/PostDetailHeader.jsx`:
- Around line 15-20: Update PostDetailHeader’s useDeletePost destructuring to
include isDeleting, and make handleDelete return immediately while a deletion is
in progress before calling deletePost. Also pass isDeleting to the delete menu
item’s disabled state when that control is available, preventing duplicate
deletion requests.
---
Nitpick comments:
In `@src/features/post/api/postMappers.js`:
- Around line 16-21: Extract the duplicated author mapping from mapPostSummary
and mapPostDetail into a shared mapAuthor helper, preserving the existing
fallback values and mapped fields. Replace both inline author object
constructions with mapAuthor(raw.author) so list and detail responses use the
same logic.
In `@src/pages/PostWritePage.jsx`:
- Around line 20-26: Move the pure post-title generation logic in buildTitle and
the category ID-to-code conversion logic around the referenced category handling
into the features/post domain, using reusable utility functions or preprocessing
associated with useCreatePost. Update PostWritePage to consume those
abstractions so it only coordinates event handlers and rendering, while
preserving the existing title and category conversion behavior.
- Around line 15-16: Move the title-building logic and
selectedCategories-to-code serialization out of PostWritePage into a reusable
utility or hook under features/post. Consolidate category data usage by choosing
one authoritative source instead of separately relying on MOCK_CATEGORIES and
POST_CATEGORIES, then update PostWritePage to consume the shared helpers so it
only manages step transitions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: a416aa7d-9ba8-4f78-a1e2-a0bbe84a19cc
📒 Files selected for processing (14)
src/constants/postCategories.jssrc/features/post/api/postMappers.jssrc/features/post/api/useCreatePost.jssrc/features/post/api/useDeletePost.jssrc/features/post/api/useLike.jssrc/features/post/api/usePostDetail.jssrc/features/post/api/usePosts.jssrc/features/post/api/useUpdatePost.jssrc/features/post/components/FilterChips.jsxsrc/features/post/components/FilterModal.jsxsrc/features/post/components/PostDetailHeader.jsxsrc/pages/HomePage.jsxsrc/pages/PostWritePage.jsxsrc/pages/ProfilePage.jsx
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| const myUser = useAuthStore.getState().user; | ||
| const mockPost = { | ||
| id: crypto.randomUUID(), | ||
| title, | ||
| content, | ||
| author: { | ||
| id: myUser?.id, | ||
| name: myUser?.nickname ?? '나', | ||
| profileImage: myUser?.profileImage ?? '', | ||
| isFollowing: false, | ||
| }, | ||
| createdAt: new Date().toLocaleString(), | ||
| commentCount: 0, | ||
| likeCount: 0, | ||
| tags: (categories ?? []).map(categoryCodeToLabel), | ||
| isLiked: false, | ||
| isPrivate, | ||
| }; | ||
| MOCK_POSTS.unshift(mockPost); | ||
| return mockPost; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
mock으로 만든 게시글 객체가 mapPostDetail shape이랑 안 맞아요.
postMappers.js에 보면 mapPostDetail은 "상세 조회/생성/수정 응답 매핑 (동일한 shape)"이라고 명시돼 있는데, 여기 mock 분기는 그 shape을 따르지 않고 직접 객체를 만들어요. fileUrls, isMine, updatedAt, isUpdated가 빠져있고, 대신 mapPostDetail엔 없는 commentCount가 들어있어요.
USE_MOCK=true인 개발 환경에서 글 작성 후 상세 화면으로 이동하는 흐름이 있다면, 실제 API 모드랑 다르게 일부 필드가 undefined로 나와서 화면이 깨지거나 조건부 렌더링이 다르게 동작할 수 있어요. mock 객체도 mapPostDetail이 기대하는 필드를 채워주면 mock/real 모드 간 일관성이 생길 것 같아요.
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 19-19: Avoid using the initial state variable in setState
Context: setTimeout(resolve, 300)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/api/useCreatePost.js` around lines 19 - 41, Update the
USE_MOCK branch in the post creation flow to construct mockPost with the same
shape expected by mapPostDetail: add fileUrls, isMine, updatedAt, and isUpdated,
and remove the unsupported commentCount field. Preserve the existing author,
tags, privacy, and count values while ensuring mock and real creation responses
expose consistent fields.
| const toggleLike = async () => { | ||
| const previousIsLiked = isLiked; | ||
| const previousCount = count; | ||
| const nextIsLiked = !isLiked; | ||
| const nextCount = nextIsLiked ? count + 1 : count - 1; | ||
| setIsLiked(nextIsLiked); | ||
| setCount(nextCount); | ||
| setError(null); | ||
|
|
||
| if (USE_MOCK) { | ||
| syncMock(nextIsLiked, nextCount); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const data = nextIsLiked | ||
| ? await api.post(ENDPOINTS.posts.likes(postId)) | ||
| : await api.delete(ENDPOINTS.posts.likes(postId)); | ||
| const serverCount = data?.likeCount ?? nextCount; | ||
| setCount(serverCount); | ||
| syncMock(nextIsLiked, serverCount); | ||
| } catch (e) { | ||
| setIsLiked(previousIsLiked); | ||
| setCount(previousCount); | ||
| setError(e); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
좋아요 요청이 겹치지 않도록 막아주세요.
첫 요청이 완료되기 전에 다시 누르면 POST와 DELETE가 병렬로 실행됩니다. 응답 순서가 뒤바뀌면 isLiked와 count가 서로 다른 요청 결과를 반영해 서버 상태와 어긋날 수 있습니다. isToggling과 ref 기반 잠금으로 중복 실행을 차단하고, 호출 컴포넌트에서는 진행 중 버튼을 비활성화해주세요.
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 33-33: Avoid using the initial state variable in setState
Context: setIsLiked(nextIsLiked)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 34-34: Avoid using the initial state variable in setState
Context: setCount(nextCount)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 47-47: Avoid using the initial state variable in setState
Context: setCount(serverCount)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 50-50: Avoid using the initial state variable in setState
Context: setIsLiked(previousIsLiked)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 51-51: Avoid using the initial state variable in setState
Context: setCount(previousCount)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
[warning] 52-52: Avoid using the initial state variable in setState
Context: setError(e)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/api/useLike.js` around lines 29 - 54, Prevent overlapping
like requests in toggleLike by adding an isToggling state and ref-based lock,
returning immediately when a toggle is already in progress, and releasing the
lock in all completion paths while preserving rollback behavior on failure.
Expose isToggling from the useLike hook and update the consuming like button
component to disable the button while a toggle is in progress.
| const updatePost = async (postId, { title, content, categories, fileKeys, isPrivate }) => { | ||
| setIsSubmitting(true); | ||
| setError(null); | ||
| try { | ||
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| return null; | ||
| } | ||
| const data = await api.patch(ENDPOINTS.posts.update(postId), { | ||
| title: title ?? null, | ||
| content: content ?? null, | ||
| categories: categories ?? [], | ||
| fileKeys: fileKeys ?? null, | ||
| isPrivate, | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
categories 빠뜨리면 조용히 카테고리 전체 삭제될 수 있어요. 지금 미리 막아두면 좋을 것 같아요.
주석에 "categories는 NOT-NULL 스펙이라 항상 배열로 전달해야 함"이라고 써있는데, 정작 코드는 categories ?? []로 값이 없으면 그냥 빈 배열을 넣어버려요. 나중에 수정 화면을 연결할 때 개발자가 실수로 categories를 안 넘기면(예: 제목만 수정하려는 의도였는데), 에러 없이 그냥 카테고리가 싹 지워진 채로 PATCH가 나가버려요. "안전한 기본값"이 아니라 "조용한 데이터 유실"이 되는 케이스라, 지금 훅을 준비하는 단계에서 미리 계약을 명확히 해두면 나중에 화면 연결할 때 실수를 막을 수 있어요.
🛡️ 제안 diff
const updatePost = async (postId, { title, content, categories, fileKeys, isPrivate }) => {
setIsSubmitting(true);
setError(null);
try {
+ if (!Array.isArray(categories)) {
+ throw new Error('categories는 항상 배열로 전달해야 합니다 (NOT-NULL 스펙)');
+ }
if (USE_MOCK) {
await new Promise((resolve) => setTimeout(resolve, 300));
return null;
}
const data = await api.patch(ENDPOINTS.posts.update(postId), {
title: title ?? null,
content: content ?? null,
- categories: categories ?? [],
+ categories,
fileKeys: fileKeys ?? null,
isPrivate,
});📝 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.
| const updatePost = async (postId, { title, content, categories, fileKeys, isPrivate }) => { | |
| setIsSubmitting(true); | |
| setError(null); | |
| try { | |
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| return null; | |
| } | |
| const data = await api.patch(ENDPOINTS.posts.update(postId), { | |
| title: title ?? null, | |
| content: content ?? null, | |
| categories: categories ?? [], | |
| fileKeys: fileKeys ?? null, | |
| isPrivate, | |
| }); | |
| const updatePost = async (postId, { title, content, categories, fileKeys, isPrivate }) => { | |
| setIsSubmitting(true); | |
| setError(null); | |
| try { | |
| if (!Array.isArray(categories)) { | |
| throw new Error('categories는 항상 배열로 전달해야 합니다 (NOT-NULL 스펙)'); | |
| } | |
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| return null; | |
| } | |
| const data = await api.patch(ENDPOINTS.posts.update(postId), { | |
| title: title ?? null, | |
| content: content ?? null, | |
| categories, | |
| fileKeys: fileKeys ?? null, | |
| isPrivate, | |
| }); |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 18-18: Avoid using the initial state variable in setState
Context: setTimeout(resolve, 300)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/api/useUpdatePost.js` around lines 14 - 28, Update
updatePost so categories is required rather than silently defaulting to an empty
array: validate that the caller provides an array and reject the update before
api.patch when it is missing or invalid. Continue sending the validated
categories array for valid requests, preserving the existing update behavior for
other fields.
| const { deletePost, error: deleteError } = useDeletePost(); | ||
|
|
||
| const handleDelete = async () => { | ||
| const ok = await deletePost(post.id); | ||
| if (ok) navigate('/'); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
삭제 진행 중에는 재실행을 차단해주세요.
useDeletePost의 isDeleting을 사용하지 않아 요청 중에도 삭제 메뉴를 다시 실행할 수 있습니다. isDeleting을 구조 분해해 handleDelete에서 조기 반환하고, 가능하면 메뉴 항목도 비활성화해 중복 요청을 막아주세요.
수정 예시
- const { deletePost, error: deleteError } = useDeletePost();
+ const { deletePost, isDeleting, error: deleteError } = useDeletePost();
const handleDelete = async () => {
+ if (isDeleting) return;
const ok = await deletePost(post.id);
if (ok) navigate('/');
};📝 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.
| const { deletePost, error: deleteError } = useDeletePost(); | |
| const handleDelete = async () => { | |
| const ok = await deletePost(post.id); | |
| if (ok) navigate('/'); | |
| }; | |
| const { deletePost, isDeleting, error: deleteError } = useDeletePost(); | |
| const handleDelete = async () => { | |
| if (isDeleting) return; | |
| const ok = await deletePost(post.id); | |
| if (ok) navigate('/'); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/components/PostDetailHeader.jsx` around lines 15 - 20,
Update PostDetailHeader’s useDeletePost destructuring to include isDeleting, and
make handleDelete return immediately while a deletion is in progress before
calling deletePost. Also pass isDeleting to the delete menu item’s disabled
state when that control is available, preventing duplicate deletion requests.
# Conflicts: # src/pages/PostWritePage.jsx # src/pages/ProfilePage.jsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/PostWritePage.jsx (1)
21-29: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win이모지 경계에서 제목을 깨뜨릴 수 있습니다.
length와slice는 UTF-16 코드 단위 기준이라 절단 위치가 이모지 중간이면 잘못된 surrogate가 생성되어�로 표시될 수 있습니다. 코드 포인트 또는 grapheme 기준으로 절단해주세요.수정 예시
function buildTitle(content) { const firstLine = content.trim().split('\n')[0]; if (!firstLine) return '제목 없음'; - return firstLine.length > TITLE_MAX_LENGTH - ? `${firstLine.slice(0, TITLE_MAX_LENGTH - 3)}...` + const characters = Array.from(firstLine); + return characters.length > TITLE_MAX_LENGTH + ? `${characters.slice(0, TITLE_MAX_LENGTH - 3).join('')}...` : firstLine; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 21 - 29, Update buildTitle to truncate titles by Unicode code points or grapheme clusters rather than UTF-16 code units, ensuring the ellipsis length limit never splits an emoji or other user-perceived character. Preserve the existing fallback for empty content and the current 20-character title limit and suffix behavior.
🧹 Nitpick comments (2)
src/pages/PostWritePage.jsx (2)
15-15: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win무료 글에서도 템플릿 API 호출이 발생합니다.
useTemplates()는postType === 'free'일 때도 실행되며,src/features/post/api/useTemplates.js(Line 7–39)의 effect는 항상 템플릿 조회를 시작합니다.enabled옵션을 추가하거나 템플릿 작성 UI를 자식 컴포넌트로 분리해 무료 글에서는 요청하지 않도록 해주세요.Also applies to: 41-53
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` at line 15, Update the PostWritePage component’s useTemplates flow so template fetching is disabled when postType is 'free'. Prefer passing an enabled condition through the useTemplates API and ensure its effect skips the request when disabled, while preserving template loading for eligible post types.
40-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift페이지에 게시글 작성 도메인 로직이 과도하게 집중되어 있습니다.
템플릿 상태 조합, 파일 누적, 카테고리 코드 변환, 생성 payload 및 성공 처리가
src/pages/PostWritePage.jsx에 직접 모여 있습니다. 페이지는 라우팅과 화면 조합만 담당하도록features/post아래 작성 훅 또는 컨테이너로 분리해주세요.As per path instructions:
src/pages/**의 페이지 컴포넌트는 얇게 유지하고, 비즈니스 로직과 복잡한 UI는features/하위 도메인으로 분리해야 합니다.Also applies to: 73-99
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 40 - 65, Refactor PostWritePage so it only handles routing and composing the screen, moving post-creation domain logic into a dedicated hook or container under features/post. Extract template-state handling, file accumulation from handleEvidenceUpload, category-code conversion, creation payload construction, and success handling; keep PostWritePage focused on passing the resulting state and callbacks to the feature UI.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/pages/PostWritePage.jsx`:
- Around line 21-29: Update buildTitle to truncate titles by Unicode code points
or grapheme clusters rather than UTF-16 code units, ensuring the ellipsis length
limit never splits an emoji or other user-perceived character. Preserve the
existing fallback for empty content and the current 20-character title limit and
suffix behavior.
---
Nitpick comments:
In `@src/pages/PostWritePage.jsx`:
- Line 15: Update the PostWritePage component’s useTemplates flow so template
fetching is disabled when postType is 'free'. Prefer passing an enabled
condition through the useTemplates API and ensure its effect skips the request
when disabled, while preserving template loading for eligible post types.
- Around line 40-65: Refactor PostWritePage so it only handles routing and
composing the screen, moving post-creation domain logic into a dedicated hook or
container under features/post. Extract template-state handling, file
accumulation from handleEvidenceUpload, category-code conversion, creation
payload construction, and success handling; keep PostWritePage focused on
passing the resulting state and callbacks to the feature UI.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: e6017036-1a01-4f72-9ec8-0d013f2b1c10
📒 Files selected for processing (4)
src/features/post/components/FilterChips.jsxsrc/features/post/components/PostDetailHeader.jsxsrc/pages/PostWritePage.jsxsrc/pages/ProfilePage.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/post/components/FilterChips.jsx
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/PostWritePage.jsx (1)
92-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win첨부파일 선택을 조용히 버리지 않도록 처리하세요.
photos와files를 수집하고 미리보기까지 표시하지만, 이 요청에는 제목·내용·카테고리·공개 여부만 전달됩니다.useCreatePost는fileKeys: null을 전송하므로 선택한 첨부파일이 게시글 등록 시 사라집니다. 파일 업로드 API가 준비되지 않은 동안에는 첨부파일 선택을 비활성화하거나, 제출을 막고 미지원 상태를 사용자에게 안내하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 92 - 99, Update the submission flow around createPost in PostWritePage to prevent selected photos or files from being silently discarded while file upload support is unavailable. Either disable attachment selection and related previews, or block submission when attachments are selected and show a clear unsupported-feature message; preserve normal post creation when no attachments are selected.
🧹 Nitpick comments (1)
src/pages/PostWritePage.jsx (1)
87-99: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift게시글 payload 조립을
features/post로 이동하세요.이 페이지가 내용 조합, 카테고리 변환, 요청 payload 생성, API 호출을 모두 수행합니다. 페이지는 상태와 이벤트 연결만 담당하도록
src/features/post/api/postMappers.js또는 별도 feature 유틸에 payload builder를 두세요. 그러면 백엔드 계약을 한 곳에서 관리하고 단위 테스트도 작성하기 쉽습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 87 - 99, Move the post payload assembly from the page’s submit flow into a reusable builder in the post feature, such as postMappers.js. Relocate the category ID-to-code conversion and fields currently passed to createPost into that builder, then have PostWritePage only provide state/event data and invoke the builder/API flow. Keep the existing payload contract and toast behavior unchanged.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/post/api/useTemplates.js`:
- Around line 7-16: Update the disabled branch in useTemplates so it resets
values and error along with isLoading, clearing all previous template data when
enabled is false. Also synchronize the enabled transition so re-enabling starts
with isLoading true and shows the loading state before the new request result.
---
Outside diff comments:
In `@src/pages/PostWritePage.jsx`:
- Around line 92-99: Update the submission flow around createPost in
PostWritePage to prevent selected photos or files from being silently discarded
while file upload support is unavailable. Either disable attachment selection
and related previews, or block submission when attachments are selected and show
a clear unsupported-feature message; preserve normal post creation when no
attachments are selected.
---
Nitpick comments:
In `@src/pages/PostWritePage.jsx`:
- Around line 87-99: Move the post payload assembly from the page’s submit flow
into a reusable builder in the post feature, such as postMappers.js. Relocate
the category ID-to-code conversion and fields currently passed to createPost
into that builder, then have PostWritePage only provide state/event data and
invoke the builder/API flow. Keep the existing payload contract and toast
behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 199a5906-ff6e-48ec-b3db-80342142f067
📒 Files selected for processing (3)
src/features/post/api/postMappers.jssrc/features/post/api/useTemplates.jssrc/pages/PostWritePage.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/post/api/postMappers.js
| export function useTemplates(type = 'BASIC', enabled = true) { | ||
| const [values, setValues] = useState([]); | ||
| const [isLoading, setIsLoading] = useState(true); | ||
| const [isLoading, setIsLoading] = useState(enabled); | ||
| const [error, setError] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| if (!enabled) { | ||
| setIsLoading(false); | ||
| return; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
enabled가 false일 때 이전 템플릿 상태를 초기화하세요.
현재 분기는 isLoading만 false로 변경하고 values와 error는 유지합니다. PostWritePage.jsx에서 postType을 free에서 템플릿 타입으로 변경하면 새 요청이 시작되기 전에 이전 템플릿 필드나 이전 오류가 잠시 표시될 수 있습니다.
비활성화 시 이전 결과와 오류를 함께 초기화하세요. 재활성화 첫 렌더에서 로딩 상태가 먼저 표시되도록 enabled 전환도 동기화해야 합니다.
수정 예시
if (!enabled) {
+ setValues([]);
+ setError(null);
setIsLoading(false);
return;
}As per path instructions, src/features/post/api/**의 커스텀 훅은 로딩 및 에러 상태를 올바르게 처리해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/api/useTemplates.js` around lines 7 - 16, Update the
disabled branch in useTemplates so it resets values and error along with
isLoading, clearing all previous template data when enabled is false. Also
synchronize the enabled transition so re-enabling starts with isLoading true and
shows the loading state before the new request result.
Source: Path instructions
seooyuun
left a comment
There was a problem hiding this comment.
안녕하세요! 프론트엔드장 최서윤입니다! SWS 기간동안 개발하느라 수고 많으셨어요!!😊
이번 PR에서 게시글 CRUD와 좋아요 동작까지 커스텀 훅으로 깔끔하게 분리해 주셨고, 특히 useLike에서 낙관적 업데이트 패턴을 적용해 사용자 경험을 챙겨주신 부분이 인상적이었습니다👏🏻👏🏻
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| const myUser = useAuthStore.getState().user; | ||
| const mockPost = { | ||
| id: crypto.randomUUID(), | ||
| title, | ||
| content, | ||
| author: { | ||
| id: myUser?.id, | ||
| name: myUser?.nickname ?? '나', | ||
| profileImage: myUser?.profileImage ?? '', | ||
| isFollowing: false, | ||
| }, | ||
| createdAt: new Date().toLocaleString(), | ||
| commentCount: 0, | ||
| likeCount: 0, | ||
| tags: (categories ?? []).map(categoryCodeToLabel), | ||
| isLiked: false, | ||
| isPrivate, | ||
| }; |
There was a problem hiding this comment.
USE_MOCK 환경의 mockPost 객체가 mapPostDetail 응답 스펙과 다르게 작성되어 있습니다.
현재 mapPostDetail 스펙에는 없는 commentCount가 포함되어 있고, 필수 필드인 fileUrls, isMine, updatedAt, isUpdated가 누락되어 있어요! 개발 환경(USE_MOCK=true)에서도 실제 API 모드와 동일한 객체 형태를 유지하도록 아래와 같이 수정하면 mock/real 모드 간 일관성을 확보할 수 있습니다.
또한 createdAt의 경우 toLocaleString()을 사용하면 사용자 디바이스 언어 설정에 따라 포맷이 달라질 수 있어, API 표준 규격인 toISOString()으로 맞춰주는 것을 추천해 드립니다!
| } catch (e) { | ||
| setError(e); | ||
| return false; | ||
| return null; |
There was a problem hiding this comment.
현재 API 호출 및 mock 실패 시 catch 블록에서 null을 반환하도록 작성되어 있습니다.
호출부(컴포넌트/폼 제출 함수)에서 이 커스텀 훅의 결과를 처리할 때 Boolean(false)을 기대하는지, 아니면 null 체크를 수행하는지 한 번 더 확인하여 타입을 일치시켜 주시면 더 좋을 것 같아요! 👍🏻
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| const index = MOCK_POSTS.findIndex((p) => p.id === postId); | ||
| if (index !== -1) MOCK_POSTS.splice(index, 1); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
USE_MOCK 환경에서 대상 게시글을 MOCK_POSTS 배열에서 제거하는 로직을 잘 작성해 주셨습니다!
다만, 혹시라도 목록에 없는 postId가 전달되어 index === -1인 경우에는 실제 삭제 작업이 이루어지지 않더라도 return true;가 실행되게 됩니다.
요청한 대상이 실제로 잘 삭제되었는지 확실하게 검증할 수 있도록, 찾지 못했을 때는 false를 반환하거나 에러 처리를 해주면 예외 케이스 대비에 더 좋을 것 같아요!
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| const index = MOCK_POSTS.findIndex((p) => p.id === postId); | |
| if (index !== -1) MOCK_POSTS.splice(index, 1); | |
| return true; | |
| } | |
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| const index = MOCK_POSTS.findIndex((p) => p.id === postId); | |
| if (index !== -1) { | |
| MOCK_POSTS.splice(index, 1); | |
| return true; | |
| } | |
| return false; | |
| } |
| if (index !== -1) MOCK_POSTS.splice(index, 1); | ||
| return true; | ||
| } | ||
| await api.delete(ENDPOINTS.posts.remove(postId)); |
There was a problem hiding this comment.
엔드포인트 함수에 postId를 넘겨서 동적으로 URL을 생성하도록 처리해주신 점 좋습니다!
| setIsLiked(nextIsLiked); | ||
| setCount(nextCount); | ||
| setError(null); |
There was a problem hiding this comment.
API 응답을 기다리지 않고 setIsLiked와 setCount를 먼저 호출해 UI를 즉시 갱신하게 한 점 좋습니다! 👍🏻 (내부 낙관적 업데이트 처리)
한 가지 고려해 볼 점은 사용자가 좋아요 버튼을 빠르게 클릭하는 경우, 서버 요청이 순서대로 처리되지 않거나(Race Condition), 이전 closure의 isLiked 상태값을 참조하면서 state가 꼬일 위험이 있습니다.
함수 내부에서 이전 state를 기반으로 업데이트(setIsLiked(prev => !prev))하도록 보완하거나 좋아요 처리 중일 때는 버튼 클릭을 막는 로직(isPending 상태 도입 또는 lodash throttle/debounce 적용)을 고려해 봐도 좋을 것 같아요!
| const serverCount = data?.likeCount ?? nextCount; | ||
| setCount(serverCount); | ||
| syncMock(nextIsLiked, serverCount); |
There was a problem hiding this comment.
API 성공 시 서버에서 내려주는 최신 likeCount를 반영하고, 이를 다시 syncMock으로 맞추어 준 흐름이 좋습니다!
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| setPost(MOCK_POST_DETAIL); | ||
| return; | ||
| } |
There was a problem hiding this comment.
USE_MOCK 환경에서 정적 객체인 MOCK_POST_DETAIL을 그대로 반환하고 있습니다.
useCreatePost나 useLike에서 MOCK_POSTS (게시글 목록 Mock) 데이터를 직접 수정 및 추가하고 있었는데, 상세 조회 시에도 MOCK_POSTS에서 전달받은 postId와 일치하는 게시글을 먼저 찾고(MOCK_POSTS.find(...)), 없을 경우 기본 MOCK_POST_DETAIL을 보여주도록 처리하면 작성/수정/좋아요 연동 흐름이 Mock 환경에서도 훨씬 자연스럽게 이어질 것 같습니다!
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| setPost(MOCK_POST_DETAIL); | |
| return; | |
| } | |
| if (USE_MOCK) { | |
| await new Promise((resolve) => setTimeout(resolve, 300)); | |
| const foundPost = MOCK_POSTS.find((p) => p.id === postId); | |
| setPost(foundPost ?? MOCK_POST_DETAIL); | |
| return; | |
| } |
| const data = await api.get(ENDPOINTS.posts.detail(postId)); | ||
| setPost(mapPostDetail(data)); |
There was a problem hiding this comment.
이렇게 실제 API 응답 데이터(data)를 mapPostDetail 매퍼 함수로 감싸서 state에 저장해두면, 백엔드 응답 스펙 변경 시에도 매퍼 레이어에서 대응할 수 있어 구조가 깔끔해집니다 👍🏻
| if (USE_MOCK) { | ||
| await new Promise((resolve) => setTimeout(resolve, 300)); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
USE_MOCK 환경일 때 단순히 return null;을 반환하고 있습니다.
수정 성공 시 호출부에서 업데이트된 데이터를 받아 UI를 갱신할 수 있도록, Mock 모드에서도 전달받은 데이터로 MOCK_POST_DETAIL이나 MOCK_POSTS 항목을 수정해 준 뒤 해당 객체를 반환하도록 하면 좋을 것 같아요!
CodeRabbit 추가 리뷰 반영 - fileKeys가 항상 null로 전송되어 선택한 첨부파일이 조용히 사라지던 문제
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/pages/PostWritePage.jsx (1)
23-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift게시글 조합 로직을
features/post로 이동해 주세요.
buildTitle, 템플릿 본문 조합, 카테고리 코드 변환,createPostpayload 생성은 게시글 도메인 로직입니다. 이 로직을PostWritePage에 두면 페이지가 API 계약과 비즈니스 규칙을 함께 관리하게 됩니다.제목 및 payload 생성 로직을
features/post의 helper 또는 hook으로 분리하세요. 페이지는 화면 조합과 라우팅에 집중하도록 유지하세요.As per path instructions:
src/pages/**페이지 컴포넌트는 얇게 유지하고 비즈니스 로직은features/하위 도메인 폴더로 분리해야 합니다.Also applies to: 92-104
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 23 - 30, Move the post-domain logic from PostWritePage, including buildTitle, template body composition, category-code conversion, and createPost payload construction, into a helper or hook under features/post. Update PostWritePage to consume that domain API so it only handles UI composition and routing.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/PostWritePage.jsx`:
- Line 52: Rename the Boolean state in PostWritePage from attachmentBlocked to
isAttachmentBlocked and rename its setter to setIsAttachmentBlocked; update
every reference to both symbols consistently.
- Around line 80-83: PostWritePage의 첨부파일 흐름에 제거 경로가 없어 선택 후 제출이 영구 차단됩니다.
AttachmentPreviewList에 사진·파일 제거 콜백을 연결하고, 해당 콜백에서 photos/files 상태를 갱신한 뒤 두 배열이
모두 비면 attachmentBlocked를 해제하세요. 첨부 선택 시 제출 차단과 모든 첨부 제거 후 제출 허용을 검증하는 테스트도
추가하세요.
---
Nitpick comments:
In `@src/pages/PostWritePage.jsx`:
- Around line 23-30: Move the post-domain logic from PostWritePage, including
buildTitle, template body composition, category-code conversion, and createPost
payload construction, into a helper or hook under features/post. Update
PostWritePage to consume that domain API so it only handles UI composition and
routing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 93c30162-b313-49f6-b4ae-8e6bedb40e92
📒 Files selected for processing (1)
src/pages/PostWritePage.jsx
CodeRabbit 반영 - 첨부 선택 후 제거 경로가 없어 제출이 영구 차단되던 문제. attachmentBlocked -> isAttachmentBlocked, 별도 state 대신 photos/files 길이로 파생.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/pages/PostWritePage.jsx (1)
23-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift게시글 생성 입력 조립을
features/post로 분리하세요.
buildTitle과handleUpload가 페이지에서 제목 생성, 카테고리 코드 변환, 생성 payload 조립을 수행합니다. 이 로직을features/post의 mapper 또는 훅으로 이동하세요. 페이지는 단계 전환과 컴포넌트 연결을 담당하게 하세요. 변환 규칙과 API 입력을 단위 테스트하기도 쉬워집니다.As per path instructions:
src/pages/**의 페이지 컴포넌트는 얇게 유지하고, 비즈니스 로직은features/하위 도메인 폴더로 분리해야 합니다.Also applies to: 96-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/PostWritePage.jsx` around lines 23 - 29, Move the post-creation business logic from PostWritePage, including buildTitle and handleUpload’s category-code conversion and payload assembly, into an appropriate mapper or hook under features/post. Keep title truncation and API input transformation rules unchanged, and leave the page responsible only for step transitions and component wiring.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/post/components/AttachmentPreviewList.jsx`:
- Around line 26-32: Update the attachment rendering in the files.map callback
by wrapping attachment.file.name in a span configured with min-width: 0,
overflow: hidden, and text-overflow: ellipsis. Set RemoveButton to flex-shrink:
0 so it remains visible when FileChip contains long filenames.
In `@src/pages/PostWritePage.jsx`:
- Around line 144-149: Update the PostWritePage step flow so attachments remain
manageable after handleNext advances to step 2: render AttachmentPreviewList,
including its removal handlers, in step 2 or provide a way to return to step 1.
Ensure the hasAttachments upload/submit state no longer leaves the user blocked,
preserving the flow of selecting attachments, advancing, removing them, and
registering the post.
---
Nitpick comments:
In `@src/pages/PostWritePage.jsx`:
- Around line 23-29: Move the post-creation business logic from PostWritePage,
including buildTitle and handleUpload’s category-code conversion and payload
assembly, into an appropriate mapper or hook under features/post. Keep title
truncation and API input transformation rules unchanged, and leave the page
responsible only for step transitions and component wiring.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8fe423fc-f57e-4342-a2d3-5f5d23e2e49f
📒 Files selected for processing (2)
src/features/post/components/AttachmentPreviewList.jsxsrc/pages/PostWritePage.jsx
| {files.map((attachment) => ( | ||
| <FileChip key={attachment.id}>{attachment.file.name}</FileChip> | ||
| <FileChip key={attachment.id}> | ||
| {attachment.file.name} | ||
| <RemoveButton onClick={() => onRemoveFile(attachment.id)} aria-label="파일 삭제"> | ||
| <CloseCircleIcon size={20} /> | ||
| </RemoveButton> | ||
| </FileChip> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
긴 파일명에서도 삭제 버튼을 보이게 하세요.
FileChip은 flex 컨테이너이고 파일명은 직접 텍스트 노드입니다. max-width: 220px와 overflow: hidden 상태에서 긴 파일명이 RemoveButton을 밀어내면 삭제 버튼이 잘릴 수 있습니다. 그러면 첨부파일 제거 UI를 사용할 수 없습니다.
파일명을 별도 span으로 감싸고 min-width: 0, overflow: hidden, text-overflow: ellipsis를 적용하세요. RemoveButton에는 flex-shrink: 0을 지정하세요.
수정 예시
<FileChip key={attachment.id}>
- {attachment.file.name}
+ <FileName>{attachment.file.name}</FileName>
<RemoveButton onClick={() => onRemoveFile(attachment.id)} aria-label="파일 삭제">
<CloseCircleIcon size={20} />
</RemoveButton>
</FileChip>
+const FileName = styled.span`
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
const RemoveButton = styled.button`
+ flex-shrink: 0;
`;Also applies to: 59-74
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 28-30: A list component should have a key to prevent re-rendering
Context: <RemoveButton onClick={() => onRemoveFile(attachment.id)} aria-label="파일 삭제">
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(list-component-needs-key)
[warning] 29-29: A list component should have a key to prevent re-rendering
Context:
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(list-component-needs-key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/post/components/AttachmentPreviewList.jsx` around lines 26 - 32,
Update the attachment rendering in the files.map callback by wrapping
attachment.file.name in a span configured with min-width: 0, overflow: hidden,
and text-overflow: ellipsis. Set RemoveButton to flex-shrink: 0 so it remains
visible when FileChip contains long filenames.
| <AttachmentPreviewList | ||
| photos={photos} | ||
| files={files} | ||
| onRemovePhoto={handleRemovePhoto} | ||
| onRemoveFile={handleRemoveFile} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
2단계에서도 첨부파일을 제거할 수 있게 하세요.
AttachmentPreviewList는 step === 1 블록 안에 있습니다. handleNext 후에는 삭제 버튼이 사라집니다. 반면 hasAttachments가 참이면 Line 190의 업로드 버튼이 비활성화됩니다. 2단계에는 1단계로 돌아가는 동작도 없습니다.
따라서 첨부파일을 선택한 뒤 다음 단계로 이동하면 사용자가 첨부파일을 제거할 수도, 게시글을 등록할 수도 없습니다. 미리보기와 삭제 버튼을 2단계에도 표시하거나 1단계로 돌아가는 버튼을 제공하세요. 첨부파일 선택 → 다음 → 삭제 → 등록 가능 흐름을 테스트하세요.
Also applies to: 186-190
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/PostWritePage.jsx` around lines 144 - 149, Update the PostWritePage
step flow so attachments remain manageable after handleNext advances to step 2:
render AttachmentPreviewList, including its removal handlers, in step 2 or
provide a way to return to step 1. Ensure the hasAttachments upload/submit state
no longer leaves the user blocked, preserving the flow of selecting attachments,
advancing, removing them, and registering the post.
☘️ 작업한 이슈
🍀 작업한 내용
🍃 작업 포인트
src/constants/postCategories.js). 명세에서 확인된 3종(LEADERSHIP/COMMUNICATION/PLANNING) 외 9종은 제가 임시로 지정한 코드라 백엔드 확인이 필요합니다.fileKeys)는 업로드 API가 아직 없어 생성 시 항상 null로 보내고 있어요. 업로드 연동은 별도 작업에서 진행할 예정입니다.USE_MOCK, raw→app 매핑 함수 분리)을 참고해서 통일감 있게 작성했습니다.Summary by CodeRabbit