Repository navigation
fix: 신고 API 백엔드 개편 대응 (POST /reports, targetType/reason) - #404
Conversation
- 신고 생성/목록 엔드포인트 변경 (/members/report→/reports, /reports/me) - 요청 구조 변경: reportType → targetType + targetId + reason - ReportModal: 선택지를 신고 사유(GENERAL/INSULT/INAPPROPRIATE_CONTENT/SPAM)로 변경 - 호출부별 targetType 주입 (MEMBER/BOOK_STORY(_COMMENT)/CLUB_NOTICE_COMMENT/CLUB_TOPIC/CLUB_BOOK_REVIEW) - 내 신고 목록 응답 필드 remap (displayName/displayImageUrl/reasonDescription) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR updates the reporting flow to use ChangesReport Contract Refactor
Home and layout adjustments
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request refactors the reporting system by replacing localized types and mappings with unified types (ReportReason, ReportTargetType, ReportRequest, etc.) and updating the corresponding API endpoints to /reports and /reports/me. It also updates various UI components to adapt to these changes. Feedback is provided regarding checking the API response success status in reportService.ts to prevent silent failures, and adding optional chaining to report.reportedAt in ReportPageClient.tsx to avoid potential runtime errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| reportMember: async (body: ReportRequest): Promise<number> => { | ||
| const res = await apiClient.post<ReportCreateResponse>( | ||
| MEMBER_ENDPOINTS.REPORT, | ||
| body | ||
| ); | ||
|
|
||
| return res.result; | ||
| }, |
There was a problem hiding this comment.
apiClient.post 호출 후 응답의 성공 여부(res.isSuccess)를 확인하지 않고 바로 res.result를 반환하고 있습니다.
apiClient는 요청이 실패하더라도 자동으로 에러를 던지지 않고 isSuccess: false인 응답 객체를 반환하므로, 이 상태에서 에러를 명시적으로 던져주지 않으면 호출부(Mutation 및 UI)에서 실패를 성공으로 오인하여 "신고가 접수되었습니다."와 같은 잘못된 피드백을 노출하게 됩니다.
memberService와 동일하게 isSuccess 여부를 체크하여 에러를 던지도록 수정해야 합니다.
reportMember: async (body: ReportRequest): Promise<number> => {
const res = await apiClient.post<ReportCreateResponse>(
MEMBER_ENDPOINTS.REPORT,
body
);
if (!res.isSuccess) {
throw new Error(res.message || "신고 접수에 실패했습니다.");
}
return res.result!;
},| content={report.content} | ||
| date={report.reportDate.substring(0, 10).replace(/-/g, ".")} | ||
| profileImageUrl={report.reportedMemberProfileImageUrl} | ||
| date={report.reportedAt.substring(0, 10).replace(/-/g, ".")} |
There was a problem hiding this comment.
report.reportedAt 값에 대해 바로 substring 메서드를 호출하고 있습니다.
API 응답 데이터의 유효성 검증 및 방어적 프로그래밍(Defensive Programming) 관점에서, 만약 reportedAt 값이 누락되거나 null/undefined로 내려올 경우 런타임 에러(TypeError)가 발생하여 전체 페이지가 렌더링되지 않고 크래시될 위험이 있습니다.
옵셔널 체이닝(?.)과 기본값 처리를 적용하고, 더 현대적인 slice 메서드를 사용하여 안전하게 문자열을 처리하도록 개선하는 것을 권장합니다.
| date={report.reportedAt.substring(0, 10).replace(/-/g, ".")} | |
| date={report.reportedAt?.slice(0, 10).replace(/-/g, ".") ?? ""} |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src/components/base-ui/Comment/comment_section_notice.tsx (1)
47-50: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider removing unused
reportedMemberNicknamefrom state.The
reportTargetstate still includesreportedMemberNickname, buthandleSubmitReportno longer uses it—onlycommentIdis used fortargetId. This is a minor cleanup opportunity.♻️ Suggested cleanup
const [reportTarget, setReportTarget] = useState<{ commentId: number; - reportedMemberNickname: string; } | null>(null);And update
handleReportComment:setReportTarget({ commentId: id, - reportedMemberNickname: target.authorName, });🤖 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/components/base-ui/Comment/comment_section_notice.tsx` around lines 47 - 50, The reportTarget state declaration includes an unused reportedMemberNickname property that is never consumed by handleSubmitReport, which only uses commentId for the targetId. Remove the reportedMemberNickname field from the state type definition in the useState hook, and then update the handleReportComment function to not set or pass this property when updating the reportTarget state.src/components/common/modals/report-block/ReportModal.tsx (1)
34-44: 🧹 Nitpick | 🔵 TrivialExtract form reset logic out of the event listener effect.
The effect at lines 34 and 37 calls
setReasonandsetReportContentsynchronously whenisOpenchanges, which triggers a second render and matches thereact-hooks/set-state-in-effectrule. This is unnecessary—the effect should only manage the Escape listener lifecycle. Compare with similar modals likeActionSelectionModal.tsxandBlockConfirmModal.tsxin the same directory, which handle only the listener without state mutations.Instead, reset form state when the modal closes by calling a dedicated function from the close handler:
+ const resetForm = () => { + setReason(defaultReason); + setReportContent(""); + }; + + const handleClose = () => { + resetForm(); + onClose(); + }; + useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) { - onClose(); + handleClose(); } }; if (isOpen) { document.addEventListener("keydown", handleEscape); - setReason(defaultReason); - } else { - // Reset state when closed - setReason(defaultReason); - setReportContent(""); } return () => { document.removeEventListener("keydown", handleEscape); }; - }, [isOpen, onClose, defaultReason]); + }, [isOpen, handleClose]);This eliminates the extra render and aligns the component with the modal patterns already established in your codebase.
🤖 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/components/common/modals/report-block/ReportModal.tsx` around lines 34 - 44, Extract the form reset logic (setReason and setReportContent calls) out of the current useEffect hook in ReportModal.tsx. Keep only the event listener management (document.removeEventListener for the keydown handler and handleEscape) in the effect. Create a separate cleanup function that calls both setReason(defaultReason) and setReportContent("") and invoke this cleanup function directly from the onClose handler instead of relying on the effect to trigger the resets when isOpen changes. This ensures the effect only manages the Escape listener lifecycle and eliminates the extra render caused by the state mutations in the effect.Source: Linters/SAST tools
🤖 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/services/memberService.ts`:
- Around line 134-136: The condition `if (cursorId)` in the cursorId parameter
append block uses a falsy check which incorrectly skips the value 0, breaking
pagination when 0 is a valid cursor. Replace the truthiness check with an
explicit undefined check (such as `if (cursorId !== undefined)` or `if (cursorId
!= null)`) to allow 0 as a valid cursor value while still excluding null and
undefined values.
In `@src/services/reportService.ts`:
- Around line 7-14: The reportMember method returns res.result unconditionally
without verifying that the API call was successful. Add a check for the
isSuccess property on the response object before returning res.result. If the
API response indicates failure (isSuccess is false), handle the error
appropriately by either throwing an error or returning a failure value instead
of propagating failed submissions as successful ones.
In `@src/types/report.ts`:
- Line 25: The documentation comment for the `content` field in the report.ts
file states a 500-character maximum limit, but the ReportModal component
enforces a 400-character limit, causing a mismatch between documented and actual
behavior. Update the comment on line 25 describing the `content` field to
specify the correct 400-character maximum limit to align with the UI validation
enforced in ReportModal.
---
Nitpick comments:
In `@src/components/base-ui/Comment/comment_section_notice.tsx`:
- Around line 47-50: The reportTarget state declaration includes an unused
reportedMemberNickname property that is never consumed by handleSubmitReport,
which only uses commentId for the targetId. Remove the reportedMemberNickname
field from the state type definition in the useState hook, and then update the
handleReportComment function to not set or pass this property when updating the
reportTarget state.
In `@src/components/common/modals/report-block/ReportModal.tsx`:
- Around line 34-44: Extract the form reset logic (setReason and
setReportContent calls) out of the current useEffect hook in ReportModal.tsx.
Keep only the event listener management (document.removeEventListener for the
keydown handler and handleEscape) in the effect. Create a separate cleanup
function that calls both setReason(defaultReason) and setReportContent("") and
invoke this cleanup function directly from the onClose handler instead of
relying on the effect to trigger the resets when isOpen changes. This ensures
the effect only manages the Escape listener lifecycle and eliminates the extra
render caused by the state mutations in the effect.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: abee463b-863b-4443-a913-b99505a5da66
📒 Files selected for processing (19)
src/app/(main)/groups/[id]/bookcase/[bookId]/BookDetailPageClient.tsxsrc/app/(main)/setting/report/ReportPageClient.tsxsrc/components/base-ui/Admin/stories/bookstory_detail.tsxsrc/components/base-ui/Admin/stories/comment_section.tsxsrc/components/base-ui/BookStory/Detatil/bookstory_detail.tsxsrc/components/base-ui/Comment/comment_section_bookcase.tsxsrc/components/base-ui/Comment/comment_section_notice.tsxsrc/components/base-ui/Profile/OtherUser/ProfileUserInfo.tsxsrc/components/common/modals/report-block/BlockConfirmModal.tsxsrc/components/common/modals/report-block/ReportModal.tsxsrc/constants/report.tssrc/hooks/mutations/useMemberMutations.tssrc/hooks/mutations/useReportMemberMutations.tssrc/hooks/useReportBlockFlow.tssrc/lib/api/endpoints/member.tssrc/services/memberService.tssrc/services/reportService.tssrc/types/member.tssrc/types/report.ts
💤 Files with no reviewable changes (1)
- src/types/member.ts
| if (cursorId) { | ||
| params.append("cursorId", cursorId.toString()); | ||
| } |
There was a problem hiding this comment.
Use an explicit undefined check for cursorId.
if (cursorId) skips 0, which can break pagination if 0 is a valid cursor.
Suggested fix
- if (cursorId) {
+ if (cursorId !== undefined) {
params.append("cursorId", cursorId.toString());
}📝 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.
| if (cursorId) { | |
| params.append("cursorId", cursorId.toString()); | |
| } | |
| if (cursorId !== undefined) { | |
| params.append("cursorId", cursorId.toString()); | |
| } |
🤖 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/services/memberService.ts` around lines 134 - 136, The condition `if
(cursorId)` in the cursorId parameter append block uses a falsy check which
incorrectly skips the value 0, breaking pagination when 0 is a valid cursor.
Replace the truthiness check with an explicit undefined check (such as `if
(cursorId !== undefined)` or `if (cursorId != null)`) to allow 0 as a valid
cursor value while still excluding null and undefined values.
| /** | ||
| * 신고 생성 요청 (POST /reports) | ||
| * - targetId: MEMBER 이면 닉네임, 그 외에는 엔티티 ID 문자열 | ||
| * - content: 선택 (최대 500자) |
There was a problem hiding this comment.
Align content max-length contract with UI validation.
Line 25 documents a 500-char limit, but ReportModal currently enforces 400 chars. This drift can block valid requests if backend accepts 500.
🤖 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/types/report.ts` at line 25, The documentation comment for the `content`
field in the report.ts file states a 500-character maximum limit, but the
ReportModal component enforces a 400-character limit, causing a mismatch between
documented and actual behavior. Update the comment on line 25 describing the
`content` field to specify the correct 400-character maximum limit to align with
the UI validation enforced in ReportModal.
apiClient.post 는 isSuccess:false 여도 throw 하지 않으므로, 실패 응답을 성공으로 오인하지 않도록 memberService 와 동일하게 isSuccess 검사 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 모바일에서 독서모임/사용자추천 세로 정렬(flex-col t:flex-row) - 사용자 추천을 ListSubscribeLarge 단일 컴포넌트로 통일(모바일 전용 분기 제거) - ListSubscribeLarge 폭을 모바일 전체폭/태블릿 이상 고정폭으로 반응형 처리 - 홈 독서모임을 모임 화면과 동일한 Mybookclub으로 통일 + 검색/생성 버튼 유지 - 비로그인/추천 0건 시 모바일·태블릿 추천 숨김 및 태블릿 폭 제한(t:max-w-[332px]) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Mybookclub에 선택적 footer prop 추가 → 박스(Subbrown-4) 하단 내부에 렌더 - HomeClubSection이 검색/생성 버튼을 footer로 전달하도록 변경 - footer 기본값 undefined라 Group-Search 등 기존 사용처 영향 없음 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 전체보기 토글이 보일 때(목록이 한도 초과)는 검색/생성 버튼 숨김, 한도 이내(0개 포함)일 때만 노출 - 태블릿에서 모임 박스를 늘리지 않고 336px 고정폭으로 변경(책이야기 카드와 동일) - 모임/추천 박스를 gap-5 + 가운데 정렬로 배치해 아래 책이야기 2열 그리드와 폭·정렬 일치 - 불필요해진 t:max-w-[332px] 조건 클래스 제거 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Mybookclub에 선택적 singleColumn prop 추가 → true면 전 화면 1열, 기본은 기존 태블릿 2열 유지 - 홈(HomeClubSection)에서 singleColumn 전달 → 태블릿 모임 목록을 데스크탑과 동일하게 1열로 표시 - 모임 검색 페이지(GroupsPageClient)는 prop 미전달이라 태블릿 2열 그대로 유지 - 토글 기준은 태블릿 limit=4 유지(5개 이상부터 전체보기) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ListSubscribeLarge에 hideTitle prop 추가 → 박스 안 "사용자 추천" 제목 숨김 옵션 - HomeRecommendationSection이 독서모임과 동일한 스타일/글자크기로 제목을 박스 위에 렌더(hideTitle 전달) - 추천 박스 높이를 고정(h-[424px]) → h-auto로 변경해 모바일/태블릿 하단 빈 공간 제거 - 데스크탑 주입(HomeStoryList)·책이야기 페이지(StoriesPageClient)는 기존 내부 제목/고정 높이 유지 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 본문 컨테이너가 태블릿에서 패딩 0이라 제목/본문이 가장자리에 붙어 잘리던 문제 해결 - 홈/메인과 동일한 px-4 t:px-6 적용, 데스크탑에서는 히어로 상단 내비와 정렬 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
수고하셨습니다 ! 이 PR 은 충돌 확인만 해주시면 될 것 같습니당 |
refactor: 홈 화면 반응형 정리 및 모임·추천 컴포넌트 통일 (+ 소식 상세 여백 보정)
충돌 해결: src/components/base-ui/Comment/comment_section_notice.tsx - import 블록 양쪽 모두 유지 (ReportReason + isValidUrl/DEFAULT_PROFILE_IMAGE) - 신고 API 개편(fix-403)과 댓글 프로필 폴백(dev) 변경은 서로 다른 영역이라 둘 다 반영 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/base-ui/home/Club/HomeClubSection.tsx (1)
68-75: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueNon-descriptive
alton decorative icon.The plus icon uses
alt="icon_plus", which is read literally by screen readers and adds noise next to the existing "모임 생성하기" label. Since the icon is decorative here, prefer an empty alt.Proposed tweak
<Image src="/icon_plus.svg" - alt="icon_plus" + alt="" width={24} height={24} className="w-3.5 h-3.5 t:w-6 t:h-6" />🤖 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/components/base-ui/home/Club/HomeClubSection.tsx` around lines 68 - 75, The plus icon in HomeClubSection is decorative, but its Image alt text is descriptive and gets read by screen readers. Update the Image in HomeClubSection to use an empty alt for the plus icon, keeping the visible "모임 생성하기" text as the accessible label.
🤖 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.
Nitpick comments:
In `@src/components/base-ui/home/Club/HomeClubSection.tsx`:
- Around line 68-75: The plus icon in HomeClubSection is decorative, but its
Image alt text is descriptive and gets read by screen readers. Update the Image
in HomeClubSection to use an empty alt for the plus icon, keeping the visible
"모임 생성하기" text as the accessible label.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 365a2cdb-f477-4336-8989-23058516ebad
📒 Files selected for processing (9)
src/app/(main)/home/HomePageClient.tsxsrc/app/(main)/news/[id]/NewsDetailClient.tsxsrc/components/base-ui/Comment/comment_section_notice.tsxsrc/components/base-ui/Group-Search/search_mybookclub.tsxsrc/components/base-ui/home/Club/HomeClubSection.tsxsrc/components/base-ui/home/Recommendation/HomeRecommendationSection.tsxsrc/components/base-ui/home/Recommendation/list_subscribe_large.tsxsrc/hooks/mutations/useMemberMutations.tssrc/services/reportService.ts
✅ Files skipped from review due to trivial changes (1)
- src/app/(main)/news/[id]/NewsDetailClient.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- src/hooks/mutations/useMemberMutations.ts
- src/services/reportService.ts
- src/components/base-ui/Comment/comment_section_notice.tsx


📌 개요 (Summary)
POST /members/report→POST /reports, 요청 구조 변경) 프론트엔드 신고하기가 동작하지 않던 문제를 수정합니다./v3/api-docs) 및 BE 소스로 신규 스펙 교차 확인 후 반영했습니다.🛠 변경 사항 (Changes)
상세
/reports, 내 신고 목록/reports/me{ reportedMemberNickname, reportType }→{ targetType, targetId, reason, content }reason(사유):GENERAL/INSULT/INAPPROPRIATE_CONTENT/SPAMtargetType:MEMBER/BOOK_STORY/BOOK_STORY_COMMENT/CLUB_NOTICE_COMMENT/CLUB_TOPIC/CLUB_BOOK_REVIEW등onSubmit(reason, content)targetType/targetId주입 (프로필=MEMBER, 댓글=*_COMMENT, 발제/한줄평=CLUB_TOPIC/CLUB_BOOK_REVIEW), 기존mapReportTypeToApi제거reasonDescription/displayName/displayImageUrl/reportedAt)📸 스크린샷 (Screenshots)
(신고 모달 사유 선택 / 차단 모달 버튼 위치 — 필요 시 첨부)
✅ 체크리스트 (Checklist)
pnpm build)pnpm lint) —Summary by CodeRabbit