Conversation
gxuoo
left a comment
There was a problem hiding this comment.
우선 프론트엔드가 처음이신데 구현하시느라 고생 많으셨습니다. 다만, 협업하는 입장에서 몇 가지 말씀드리고 싶은 것들이 있으니 참고해주시면 감사하겠습니다.
- 노션에 있는
규칙모두 자세하게 꼼꼼히 읽어보고 지키기 - Figma에 디자이너님이 구현해주신 디자인 최대한 비슷하게라도 만들기 (모르겠으면 물어보세요)
- GPT 사용은 괜찮지만, 만들어준 것을 가지고 코드를 이해하고 디자인에 맞게 수정해보시는 연습을 해주시면 좋겠습니다. 현재 제가 몇 가지
Request Change를 달아드렸는데 이것 말고도 안 맞는 게 너무 많아요...
프론트엔드는 사용자와 제일 가까이서 상호작용하는 분야로 UI, UX 등 신경써야할 것들이 굉장히 많습니다. 저도 아직 배우는 입장에서 많은 걸 알고 있는 건 아니지만 제가 학습하면서 느꼈던 것을 위주로 창민님에게 리뷰를 드리고 있으니 지금하시는 것처럼 열심히 해주시면 감사하겠습니다.
frontend/src/pages/Board.jsx
Outdated
| </div> | ||
| ); | ||
|
|
||
| function Board() { |
There was a problem hiding this comment.
화살표 함수 형식으로 변경해주시면 좋을 것 같아요~
There was a problem hiding this comment.
board는 전체에서 쓰는 함수인 줄 착각했습니다. 수정하겠습니다.
There was a problem hiding this comment.
CreatePostModal은 function으로 유지하면 될까요?
There was a problem hiding this comment.
CreatePostModal도 화살표 함수로 변경해주세요
frontend/src/pages/Board.module.css
Outdated
| .header h1 { | ||
| font-size: 32px; | ||
| font-weight: bold; | ||
| color: #1e293b; | ||
| margin: 0; | ||
| } |
There was a problem hiding this comment.
| .header h1 { | |
| font-size: 32px; | |
| font-weight: bold; | |
| color: #1e293b; | |
| margin: 0; | |
| } | |
| .header h1 { | |
| font-size: 36px; | |
| font-weight: semibold; | |
| color: #171717; | |
| margin: 0; | |
| } |
디자이너님이 만들어주신 font-size, weight, color 등 요소들에 대해서 Figma에서 확인할 수 있으니 다음에 작업하실 땐 Figma 디자인 바탕으로 CSS 요소들을 변경해주세요.
frontend/src/pages/Board.module.css
Outdated
| .searchBar { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| background-color: white; | ||
| border: 2px solid #e2e8f0; | ||
| border-radius: 12px; | ||
| padding: 16px 20px; | ||
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | ||
| } |
There was a problem hiding this comment.
| .searchBar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background-color: white; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | |
| } | |
| .searchBar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background-color: white; | |
| border: 0.8px solid #e2e8f0; | |
| border-radius: 50px; | |
| padding: 24px 26px; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | |
| } |
frontend/src/pages/Board.jsx
Outdated
| import styles from './Board.module.css'; | ||
| import CreatePostModal from '../components/Board/CreatePostModal'; | ||
|
|
||
| const PostItem = ({ title, author, time, content, authorProfile }) => ( |
There was a problem hiding this comment.
하나의 .jsx 파일에는 하나의 컴포넌트만 있는 게 좋아요. PostItem 이라는 컴포넌트를 만들어주셨으면 components 폴더에 새로운 파일을 만들어서 Board.jsx 에서 import 하는 방식으로 변경해주세요.
당연히 css 파일도 분리해야겠죠?
frontend/src/pages/Board.jsx
Outdated
| <div className={styles.authorDetails}> | ||
| <span className={styles.authorName}>{author}</span> | ||
| <span className={styles.postTime}>{time}</span> | ||
| </div> |
There was a problem hiding this comment.
이름 옆에 게시판 게시 시간이 있어야 하는데 현재는 이름 아래에 위치하네요 수정부탁드립니다.
| <button className={styles.closeButton} onClick={onClose}> | ||
| × | ||
| </button> |
There was a problem hiding this comment.
Modal 내에 내용을 작성하고 닫으면 작성했던 내용들이 모두 초기화 되어야 사용자 흐름에서는 올바른 작동이라고 생각해요.
하지만, 현재 onClick 이벤트로 발생되는 onClose 는 부모의 props로 받아오는 Board.jsx의 handleCloseModal 함수인데, 해당 함수에서는 Modal을 닫아주기만 하고 작성했던 내용을 초기화하는 로직은 존재하지 않아요. 고민해보시고 수정부탁드립니다.
Co-authored-by: 규영 <121455054+gxuoo@users.noreply.github.com>
Co-authored-by: 규영 <121455054+gxuoo@users.noreply.github.com>
Co-authored-by: 규영 <121455054+gxuoo@users.noreply.github.com>
Co-authored-by: 규영 <121455054+gxuoo@users.noreply.github.com>
Co-authored-by: 규영 <121455054+gxuoo@users.noreply.github.com>
| <div className={styles.header}> | ||
| <img src={ProfileIcon} className={styles.authorImage} alt="프로필" /> | ||
| <span className={styles.author}>운영진</span> | ||
| <span className={styles.dot}></span> |
There was a problem hiding this comment.
해당 태그를 사용하지 않고 구현하는 방법은 없을까요?
There was a problem hiding this comment.
dot 이 태그는 아니죠? span 태그에 dot이라는 스타일 클래스를 입힌 거 아닌가요?? 해당 태그가 있을 필요는 없어보입니다 다른 css 요소로 해결할 수 있다고 생각이 들어요.
There was a problem hiding this comment.
span 태그 말고 다른 요소를 사용하라는 말씀이신건가요?
There was a problem hiding this comment.
음... 제가 보기에는 그래 보였는데 사용하신 이유를 말씀해주시면 좋을 거 같아요.
| @@ -0,0 +1,26 @@ | |||
| export const getTimeAgo = (createdAt) => { | |||
There was a problem hiding this comment.
해당 코드가 components 폴더에 있는 이유가 궁금하네요.
There was a problem hiding this comment.
코딩할 당시에 git pull을 안해서 utils 폴더가 없었는데 뭔가 제가 함부로 utils 폴더 만들면 안될거같아서 우선 components 폴더에 넣었는데 utils 폴더로 이동시키면 될까요?
There was a problem hiding this comment.
src 폴더 내부에 utils 폴더를 만들어도 괜찮을 거 같아요. 옮겨주시겠어요?
There was a problem hiding this comment.
main 브랜치 pull 하시면 utils 폴더 있을텐데 거기로 옮겨주세요
| <div className={styles.header}> | ||
| <img src={ProfileIcon} className={styles.authorImage} alt="프로필" /> | ||
| <span className={styles.author}>운영진</span> | ||
| <span className={styles.dot}></span> |
| @@ -0,0 +1,26 @@ | |||
| export const getTimeAgo = (createdAt) => { | |||
There was a problem hiding this comment.
코딩할 당시에 git pull을 안해서 utils 폴더가 없었는데 뭔가 제가 함부로 utils 폴더 만들면 안될거같아서 우선 components 폴더에 넣었는데 utils 폴더로 이동시키면 될까요?
gxuoo
left a comment
There was a problem hiding this comment.
다시 코멘트 드렸으니 적용해주시고 다시 알려주시면 감사하겠습니다~!
| <div className={styles.header}> | ||
| <img src={ProfileIcon} className={styles.authorImage} alt="프로필" /> | ||
| <span className={styles.author}>운영진</span> | ||
| <span className={styles.dot}></span> |
There was a problem hiding this comment.
dot 이 태그는 아니죠? span 태그에 dot이라는 스타일 클래스를 입힌 거 아닌가요?? 해당 태그가 있을 필요는 없어보입니다 다른 css 요소로 해결할 수 있다고 생각이 들어요.
| @@ -0,0 +1,26 @@ | |||
| export const getTimeAgo = (createdAt) => { | |||
There was a problem hiding this comment.
src 폴더 내부에 utils 폴더를 만들어도 괜찮을 거 같아요. 옮겨주시겠어요?
gxuoo
left a comment
There was a problem hiding this comment.
리뷰가 늦었네요~! 일단 Approve로 두겠습니다.
| <div className={styles.header}> | ||
| <img src={ProfileIcon} className={styles.authorImage} alt="프로필" /> | ||
| <span className={styles.author}>운영진</span> | ||
| <span className={styles.dot}></span> |
There was a problem hiding this comment.
음... 제가 보기에는 그래 보였는데 사용하신 이유를 말씀해주시면 좋을 거 같아요.
| @@ -0,0 +1,26 @@ | |||
| export const getTimeAgo = (createdAt) => { | |||
There was a problem hiding this comment.
main 브랜치 pull 하시면 utils 폴더 있을텐데 거기로 옮겨주세요
|
Caution Review failedThe pull request is closed. Walkthrough게시판 기능을 새로 추가했습니다: 작성 모달, 게시글 카드, 검색/정렬/작성 액션 바, 게시글 상세(댓글 포함), 로컬스토리지 기반 영속성 및 상대 시간 유틸리티, 라우트(/board/:postId) 추가와 관련된 CSS 모듈과 아이콘 사용이 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Board as Board Page
participant UI as Modal / PostItem
participant Storage as localStorage
participant Detail as PostDetail
Note over Board, UI `#DDEBF7`: 게시글 목록 표시, 검색/정렬, 작성 트리거
User->>Board: 페이지 진입
Board->>Storage: boardPosts 로드
Storage-->>Board: posts 반환
Board-->>User: 목록 렌더링 (PostItem)
User->>Board: 글쓰기 버튼 클릭
Board->>UI: Modal 열기
User->>UI: 제목/내용 입력 및 저장
UI->>Storage: 새 게시글 저장 (boardPosts 업데이트)
Storage-->>Board: 저장 완료
Board-->>User: 목록 갱신
User->>PostItem: 게시글 클릭
PostItem->>Detail: 라우트 이동 (/board/:postId) + state 전달
Detail->>Storage: post 및 comments_{postId} 로드
Storage-->>Detail: 데이터 반환
Detail-->>User: 상세 + 댓글 렌더링
User->>Detail: 댓글 작성 (Ctrl+Enter)
Detail->>Storage: comments_{postId} 저장/업데이트
Storage-->>Detail: 최신 댓글 목록
Detail-->>User: 댓글 갱신 렌더링
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 주의 집중이 필요한 영역:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
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 |
gxuoo
left a comment
There was a problem hiding this comment.
동은님이 말씀하신 내용 제가 대신 커밋 올렸습니다.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
frontend/src/components/Board/PostItem.jsx (1)
5-18: 데이터 속성 이름 일관성 개선 권장
PostItem은post.date를 사용하고,Board.jsx에서도date속성으로 게시글을 생성합니다. 하지만getTimeAgo함수의 매개변수 이름은createdAt입니다. 기능적으로는 문제없지만, 코드 가독성과 유지보수를 위해 속성 이름을 통일하는 것을 권장합니다.예:
date→createdAt로 통일frontend/src/pages/Board.jsx (1)
27-31: 게시글 저장 시 유효성 검증 추가 권장현재 빈 제목이나 내용으로도 게시글을 저장할 수 있습니다. 사용자 경험 개선을 위해 저장 전 유효성 검증을 추가하는 것을 권장합니다.
const handleSave = () => { + if (!title.trim() || !content.trim()) { + alert('제목과 내용을 입력해주세요.'); + return; + } const newPost = { title, content, date: new Date() }; setPosts([newPost, ...posts]); handleCloseModal(); };frontend/src/utils/TimeUtils.jsx (1)
1-26: 구현 양호상대 시간 표시 로직이 잘 구현되어 있습니다. 현재 근사치 계산(30일=1달, 12달=1년)은 UI 표시용으로 충분합니다.
향후 더 정확한 날짜 계산이 필요하다면
date-fns나dayjs같은 라이브러리 사용을 고려할 수 있습니다.frontend/src/components/Board/Modal.jsx (2)
32-44: 파일 업로드 기능 구현 상태 확인파일 추가 UI는 존재하지만, 실제 파일을 처리하는 로직(state, 업로드 핸들러)이 구현되어 있지 않습니다. 현재 단계에서 의도적으로 UI만 구현한 것인지 확인이 필요합니다.
향후 백엔드 연동 시 구현 예정이라면 TODO 주석을 추가하는 것을 권장합니다. 현재 단계에서 구현이 필요하다면 도움을 드릴 수 있습니다.
32-34: 직접 DOM 조작 대신 useRef 사용 권장Line 34에서
document.getElementById()를 사용한 직접 DOM 조작은 React의 선언적 패턴과 맞지 않습니다.useRef를 사용하는 것이 더 권장됩니다.+import { useRef } from 'react'; + const Modal = ({ title, setTitle, content, setContent, onSave, onClose }) => { + const fileInputRef = useRef(null); + return ( <div className={styles.overlay} onClick={onClose}> <div className={styles.modal} onClick={(e) => e.stopPropagation()}> {/* ... */} <div className={styles.fileSection} - onClick={() => document.getElementById('fileUpload').click()} + onClick={() => fileInputRef.current?.click()} > <img src={FolderIcon} alt="폴더" /> <span className={styles.fileText}>파일추가</span> <input type="file" - id="fileUpload" + ref={fileInputRef} className={styles.fileInput} style={{ display: 'none' }} /> </div>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
frontend/src/assets/boardCloseIcon.svgis excluded by!**/*.svgfrontend/src/assets/boardFolder.svgis excluded by!**/*.svgfrontend/src/assets/boardSearchArrow.svgis excluded by!**/*.svgfrontend/src/assets/boardSelectArrow.svgis excluded by!**/*.svgfrontend/src/assets/boardSortArrow.svgis excluded by!**/*.svgfrontend/src/assets/board_plus.svgis excluded by!**/*.svgfrontend/src/assets/board_profile.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
frontend/src/components/Board/Modal.jsx(1 hunks)frontend/src/components/Board/Modal.module.css(1 hunks)frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/components/Board/PostItem.module.css(1 hunks)frontend/src/pages/Board.jsx(1 hunks)frontend/src/pages/Board.module.css(1 hunks)frontend/src/utils/TimeUtils.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/components/Board/Modal.jsx (1)
frontend/src/pages/Board.jsx (2)
title(12-12)content(13-13)
frontend/src/pages/Board.jsx (2)
frontend/src/components/Board/PostItem.jsx (1)
PostItem(5-18)frontend/src/components/Board/Modal.jsx (1)
Modal(6-76)
frontend/src/components/Board/PostItem.jsx (1)
frontend/src/utils/TimeUtils.jsx (2)
getTimeAgo(1-26)getTimeAgo(1-26)
🪛 Biome (2.1.2)
frontend/src/components/Board/PostItem.module.css
[error] 18-18: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
min-height is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
[error] 72-73: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
line-height is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🔇 Additional comments (2)
frontend/src/pages/Board.jsx (1)
56-64: 정렬 기능 미구현 확인
sortOption상태는 선언되어 있지만, 실제로 게시글 목록을 정렬하는 로직은 구현되지 않았습니다. 의도적으로 향후 구현을 위해 남겨둔 것인지 확인이 필요합니다.향후 백엔드 연동 시 구현 예정이라면 현재 상태를 유지하고, 프론트엔드에서 클라이언트 사이드 정렬이 필요하다면 다음과 같이 구현할 수 있습니다:
const sortedPosts = [...posts].sort((a, b) => { switch (sortOption) { case 'latest': return b.date - a.date; case 'oldest': return a.date - b.date; case 'popular': return (b.views || 0) - (a.views || 0); default: return 0; } });frontend/src/components/Board/Modal.module.css (1)
1-254: 모달 스타일링 적절함모달의 오버레이, 레이아웃, 폼 요소 스타일링이 체계적으로 구현되어 있습니다. 고정 크기와 반응형 최대값(
90vw,90vh)의 조합이 적절합니다.
| <div className={styles.accessField}> | ||
| <label className={styles.accessLabel}>접근 권한</label> | ||
| <div className={styles.selectWrapper}> | ||
| <select className={styles.select} defaultValue="세션선택"> | ||
| <option value="세션선택">세션선택</option> | ||
| <option value="공개">공개</option> | ||
| <option value="비공개">비공개</option> | ||
| </select> | ||
| <div className={styles.selectIcon}> | ||
| <img src={DropdownArrowIcon} alt="드롭다운 화살표" /> | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
접근 권한 선택값이 저장되지 않음
Line 58에서 select 요소가 defaultValue를 사용하고 있어, 사용자가 선택한 값이 부모 컴포넌트로 전달되지 않습니다. 게시글 저장 시 접근 권한 정보가 누락됩니다.
부모 컴포넌트에 accessControl state와 setter를 추가하고, 제어 컴포넌트로 변경해야 합니다:
Board.jsx:
const [accessControl, setAccessControl] = useState('세션선택');Modal.jsx:
-const Modal = ({ title, setTitle, content, setContent, onSave, onClose }) => {
+const Modal = ({ title, setTitle, content, setContent, accessControl, setAccessControl, onSave, onClose }) => {
// ...
- <select className={styles.select} defaultValue="세션선택">
+ <select
+ className={styles.select}
+ value={accessControl}
+ onChange={(e) => setAccessControl(e.target.value)}
+ >🤖 Prompt for AI Agents
frontend/src/components/Board/Modal.jsx around lines 55 to 67: the select uses
defaultValue so user selection isn't propagated; convert the select into a
controlled input by accepting an accessControl prop and an
onChange/setAccessControl prop from the parent, replace defaultValue with
value={accessControl} and call the setter (e.g., onChange ->
setAccessControl(event.target.value)) to update parent state; also add
accessControl state and its setter in the parent Board.jsx (initialize to
'세션선택') and pass them into Modal, and ensure the parent includes accessControl
when saving the post.
| .postItem { | ||
| width: 100%; | ||
| max-width: 908px; | ||
| box-sizing: border-box; | ||
| min-height: 120px; | ||
| background: rgba(249, 249, 249, 1); | ||
| border-radius: 12px; | ||
| padding: 28px; | ||
| margin-bottom: 16px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 12px; | ||
| word-wrap: break-word; | ||
| overflow-wrap: break-word; | ||
| word-break: break-word; | ||
| white-space: normal; | ||
| height: auto; | ||
| min-height: auto; | ||
| } |
There was a problem hiding this comment.
중복된 CSS 속성 제거 필요
min-height 속성이 중복 선언되어 있습니다 (Line 5: min-height: 120px, Line 18: min-height: auto). 마지막 선언이 이전 선언을 덮어쓰므로, Line 5의 min-height: 120px가 무효화됩니다.
의도한 동작을 명확히 하기 위해 중복을 제거해주세요.
.postItem {
width: 100%;
max-width: 908px;
box-sizing: border-box;
- min-height: 120px;
background: rgba(249, 249, 249, 1);
border-radius: 12px;
padding: 28px;
margin-bottom: 16px;
display: flex;
flex-direction: column;
gap: 12px;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-word;
white-space: normal;
height: auto;
min-height: auto;
}Based on learnings: Static analysis가 지적한 중복 속성 문제입니다.
📝 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.
| .postItem { | |
| width: 100%; | |
| max-width: 908px; | |
| box-sizing: border-box; | |
| min-height: 120px; | |
| background: rgba(249, 249, 249, 1); | |
| border-radius: 12px; | |
| padding: 28px; | |
| margin-bottom: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| word-break: break-word; | |
| white-space: normal; | |
| height: auto; | |
| min-height: auto; | |
| } | |
| .postItem { | |
| width: 100%; | |
| max-width: 908px; | |
| box-sizing: border-box; | |
| background: rgba(249, 249, 249, 1); | |
| border-radius: 12px; | |
| padding: 28px; | |
| margin-bottom: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| word-break: break-word; | |
| white-space: normal; | |
| height: auto; | |
| min-height: auto; | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 18-18: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.
min-height is already defined here.
Remove or rename the duplicate property to ensure consistent styling.
(lint/suspicious/noDuplicateProperties)
🤖 Prompt for AI Agents
In frontend/src/components/Board/PostItem.module.css around lines 1 to 19, there
are duplicate min-height declarations (line 5: min-height: 120px and line 18:
min-height: auto) which makes the first one ineffective; remove the unintended
duplicate and keep the desired behavior (e.g., retain min-height: 120px and
delete min-height: auto) so the stylesheet has a single clear min-height rule.
| .boardContainer { | ||
| width: 100%; | ||
| max-width: 1030px; | ||
| background: rgba(255, 255, 255, 1); | ||
| padding: 32px 40px; | ||
| margin-top: 80px; | ||
| margin-left: 100px; | ||
| box-sizing: border-box; | ||
| } |
There was a problem hiding this comment.
반응형 디자인 고려 필요
PR 목표에서 반응형(모바일/태블릿/데스크톱) 지원을 명시했으나, 현재 구현은 고정 너비(max-width: 1030px, margin-left: 100px)와 고정 마진을 사용하고 있습니다. 작은 화면에서는 레이아웃이 깨지거나 가로 스크롤이 발생할 수 있습니다.
미디어 쿼리를 추가하거나 margin-left를 margin: 0 auto로, 고정 너비를 퍼센트 기반 또는 min()/clamp() 함수로 대체하는 것을 권장합니다.
🤖 Prompt for AI Agents
In frontend/src/pages/Board.module.css around lines 2–10, the container uses
fixed horizontal positioning (max-width: 1030px and margin-left: 100px) which
breaks on small screens; replace the fixed margin with centered layout (margin:
0 auto), make the width responsive (use percentage or CSS functions like
min()/clamp() e.g. max-width derived from viewport such as max-width:
min(1030px, 95%)), and add simple media queries to reduce padding/margins on
mobile/tablet breakpoints so the container never overflows on small screens.
네 알겠습니다! |
- PostDetail 페이지 신규 구현 (게시글 상세 조회, 댓글 CRUD) - 게시판 전체 반응형 디자인 적용 (768px, 480px 미디어 쿼리) - 보드 아이콘 assets 추가 (하트, 북마크)
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (7)
frontend/src/components/Board/BoardActions.module.css (1)
21-31: 중복된 너비 계산을 단순화하세요.
max-width: calc(100% - 20px)와padding-right: 20px가 함께 사용되어 의도가 명확하지 않습니다. 둘 중 하나만 사용하여 코드를 단순화하는 것을 권장합니다.예시:
.boardActions { width: 100%; - max-width: calc(100% - 20px); padding-right: 20px; box-sizing: border-box;frontend/src/components/Board/BoardActions.jsx (2)
1-1: React 19에서는 React import가 필요하지 않습니다.React 19와 modern JSX transform을 사용하는 경우, JSX를 위한 React import는 더 이상 필요하지 않습니다. 제거해도 동작에는 영향이 없으나, 제거하는 것이 더 깔끔합니다.
-import React from 'react'; import styles from './BoardActions.module.css';
19-23: 장식용 아이콘의 alt 텍스트를 검토하세요.드롭다운 화살표와 플러스 아이콘은 주로 장식 목적이며 텍스트 콘텐츠가 이미 기능을 설명하고 있습니다. 스크린 리더 중복을 피하기 위해 빈 alt 속성(
alt="")을 사용하는 것을 고려하세요.<img src={DropdownArrowIcon} - alt="드롭다운" + alt="" className={styles.selectArrow} /> </div> <button className={styles.writeButton} onClick={onWrite}> <span>글 작성하기</span> - <img src={PlusIcon} alt="작성" /> + <img src={PlusIcon} alt="" /> </button>Also applies to: 26-29
frontend/src/pages/PostDetail.jsx (1)
21-32: 하드코딩된 작성자 정보를 인증 컨텍스트로 교체하세요.현재 댓글 작성자가 "사용자"로 하드코딩되어 있습니다. 향후 백엔드 연동 시 실제 사용자 정보를 사용하도록 개선이 필요합니다.
추가로,
Date.now()를 ID로 사용하면 빠른 연속 클릭 시 충돌이 발생할 수 있습니다. UUID 라이브러리나 백엔드 생성 ID를 권장합니다.frontend/src/pages/PostDetail.module.css (1)
167-194: 제출 버튼 마진 계산 방식을 개선하세요.Line 173의
margin-left: calc(var(--width-content) - 113px)계산은 반응형 레이아웃에서 취약합니다. 미디어 쿼리에서 이미 처리되고 있지만,justify-content: flex-end를 사용한 flexbox 레이아웃이 더 견고한 접근 방식입니다.대안으로 부모 컨테이너에 flexbox를 적용할 수 있습니다:
/* 부모 컨테이너에 추가 */ .buttonWrapper { display: flex; justify-content: flex-end; width: var(--width-content); } .submitButton { width: 113px; height: 43px; /* margin-left 제거 */ }frontend/src/components/Board/SearchBar.jsx (1)
6-10: 검색 기능이 아직 구현되지 않았습니다.현재
handleSearch는 콘솔 로그만 출력하고 실제 검색 기능은 구현되지 않았습니다. PR 요약에 따르면 향후 백엔드 연동이 예정되어 있습니다.백엔드 API 연동 시 필요한 검색 로직 구현을 도와드릴까요? 또는 이를 추적하기 위한 이슈를 생성해 드릴까요?
frontend/src/components/Board/SearchBar.module.css (1)
111-137: 미디어 쿼리 내에서:root변수를 재정의하는 것은 권장되지 않습니다.
:root는 전역 범위이므로 미디어 쿼리 내에서 재정의하면 문서 전체에 영향을 미칩니다. 이는 다른 컴포넌트와의 충돌을 일으킬 수 있습니다.더 나은 접근 방식:
옵션 1: 컴포넌트 범위로 변수 한정
.searchContainer { --search-height: 68px; --search-padding: 24px 16px 24px 26px; /* ... */ } @media (max-width: 768px) { .searchContainer { --search-height: 56px; --search-padding: 16px 12px 16px 20px; /* ... */ } }옵션 2: 직접 값 할당
@media (max-width: 768px) { .searchContainer { height: 56px; padding: 16px 12px 16px 20px; } .searchInput { font-size: 16px; } /* ... */ }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (4)
frontend/src/assets/boardBookMark.fill.svgis excluded by!**/*.svgfrontend/src/assets/boardBookMark.svgis excluded by!**/*.svgfrontend/src/assets/boardHeart.fill.svgis excluded by!**/*.svgfrontend/src/assets/boardHeart.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
frontend/src/App.jsx(2 hunks)frontend/src/components/Board/BoardActions.jsx(1 hunks)frontend/src/components/Board/BoardActions.module.css(1 hunks)frontend/src/components/Board/PostItem.module.css(1 hunks)frontend/src/components/Board/SearchBar.jsx(1 hunks)frontend/src/components/Board/SearchBar.module.css(1 hunks)frontend/src/pages/PostDetail.jsx(1 hunks)frontend/src/pages/PostDetail.module.css(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Board/PostItem.module.css
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/components/Board/SearchBar.jsx (1)
frontend/src/pages/Board.jsx (1)
searchTerm(10-10)
frontend/src/pages/PostDetail.jsx (1)
frontend/src/components/Board/SearchBar.jsx (1)
handleKeyPress(12-16)
frontend/src/components/Board/BoardActions.jsx (1)
frontend/src/pages/Board.jsx (1)
sortOption(15-15)
frontend/src/App.jsx (1)
frontend/src/pages/PostDetail.jsx (1)
PostDetail(7-105)
🔇 Additional comments (7)
frontend/src/components/Board/BoardActions.module.css (1)
33-116: 잘 구현되었습니다!셀렉트 박스와 버튼의 스타일링이 잘 구현되어 있습니다. 특히 다음 사항들이 좋습니다:
- 커스텀 셀렉트 박스의 네이티브 appearance 제거
- 적절한 hover, focus, active 상태 처리
- focus 시 접근성을 고려한 box-shadow 아웃라인
frontend/src/components/Board/BoardActions.jsx (1)
6-32: 컴포넌트 구조가 깔끔합니다!함수 선언 방식과 props 구조 분해 사용이 프로젝트의 코딩 가이드라인을 잘 따르고 있습니다. 제어 컴포넌트 패턴이 올바르게 적용되었고, JSX 구조가 명확합니다.
frontend/src/pages/PostDetail.jsx (1)
1-5: LGTM!임포트 구문이 올바르게 작성되었습니다.
frontend/src/pages/PostDetail.module.css (1)
1-16: LGTM!CSS 변수 정의가 잘 구성되어 있습니다.
frontend/src/components/Board/SearchBar.jsx (1)
18-37: 컴포넌트 구조와 접근성이 잘 구현되었습니다.
- 함수 선언 방식을 사용하여 PR 가이드라인을 준수했습니다.
aria-label속성을 통해 접근성을 고려했습니다.- props 처리와 이벤트 핸들링이 명확합니다.
frontend/src/components/Board/SearchBar.module.css (2)
1-27: CSS 구조와 반응형 디자인이 잘 구현되었습니다.
- CSS 변수를 활용하여 유지보수성이 좋습니다.
- 3개의 브레이크포인트(1024px, 768px, 480px)를 통한 반응형 디자인이 적절합니다.
- Flexbox를 사용한 레이아웃이 명확하고 효율적입니다.
- transition 효과와 hover/active 상태가 잘 처리되었습니다.
Also applies to: 54-101, 103-109
29-42: <!-- [scratchpad_start]
[task review_file_1/1 verify_mask_composite_support COMPLETE]
Verified browser support for mask-composite: exclude and project targets[observations]
- Vite default "modules" target: Chrome >= 87, Edge >= 88, Firefox >= 78, Safari >= 14
- mask-composite (exclude) support: Chrome 120+, Firefox (Gecko 53+), Safari 15.4+
- Project uses Vite default target with no explicit browserslist override
- Default target includes Safari 14, which does NOT support mask-composite
[analysis]
- mask-composite: exclude will NOT work in Safari 14 (Vite's default minimum)
- Gap between target (Safari 14) and feature support (Safari 15.4+) = real issue
- Original review's concern is VALID - code has compatibility gap
- Suggested fix (-webkit-mask-composite: xor) questionable:
- Safari 14 had limited/no mask-composite support even with prefix
- Not a prefix issue but a missing feature issue
[conclusion]
Review comment identifies real issue but suggests incomplete/potentially incorrect fix
Developer must: verify if Safari 14 support is required, or update browser targets, or use alternative technique[tag]
</scratchpad_end] -->
mask-composite: exclude브라우저 호환성 이슈 확인됨프로젝트의 Vite 기본 설정으로는 Safari 14 이상을 지원하도록 구성되어 있으나,
mask-composite: exclude는 Safari 15.4 이상에서만 지원됩니다. 이로 인해 Safari 14에서는 그라디언트 테두리 효과가 작동하지 않습니다.다음 중 하나를 검토하세요:
- 브라우저 타겟 업데이트: 프로젝트가 Safari 14 지원을 반드시 필요로 하는지 확인하고, 필요 없다면
vite.config.js에서build.target을safari15.4이상으로 설정- 대체 기법: Safari 14에서도 작동하는
clip-path,box-shadow, 또는 SVG 마스크 기반 방식으로 그라디언트 테두리 구현- 폴백 제공: 지원되지 않는 브라우저에서도 시각적 degradation이 수용 가능한지 확인
참고로
-webkit-mask-composite프리픽스 추가만으로는 Safari 14의 기본 미지원 문제를 해결하지 못합니다.
| /> | ||
| <Route path="/attendance-manage" element={<AttendanceManage />} /> | ||
| <Route path="/board" element={<Board />} /> | ||
| <Route path="/board/:postId" element={<PostDetail />} /> |
There was a problem hiding this comment.
라우트 파라미터와 컴포넌트 구현 간 불일치를 확인하세요.
:postId 파라미터가 정의되어 있지만, PostDetail 컴포넌트는 이를 사용하지 않고 location.state에만 의존합니다. 이는 URL 공유 및 새로고침 시 문제를 발생시킬 수 있습니다.
일관성을 위해 URL 파라미터를 활용하거나, 현재 구현의 제약사항을 명확히 문서화하는 것을 권장합니다.
PostDetail.jsx의 리뷰 코멘트도 참고하세요.
🤖 Prompt for AI Agents
In frontend/src/App.jsx around line 32, the route defines a URL param (:postId)
but the PostDetail component currently ignores it and relies only on
location.state; update PostDetail to read postId from useParams() and load the
post when location.state is absent (e.g., fetch/post lookup by id), or if you
prefer to keep the current state-only approach, remove the :postId param from
the route and document that the component requires navigation state—ensure one
consistent behavior (preferably support both: useParams as primary source and
fallback to location.state).
| <select | ||
| className={styles.sortSelect} | ||
| value={sortOption} | ||
| onChange={(e) => onSortChange(e.target.value)} | ||
| > | ||
| <option value="latest">최신순</option> | ||
| <option value="oldest">오래된순</option> | ||
| <option value="popular">인기순</option> | ||
| </select> | ||
| <img | ||
| src={DropdownArrowIcon} | ||
| alt="드롭다운" | ||
| className={styles.selectArrow} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
접근성: select 요소에 레이블이 필요합니다.
select 요소가 시각적으로 표시되지 않는 레이블 없이 사용되고 있습니다. 스크린 리더 사용자를 위해 aria-label 또는 연결된 <label> 요소를 추가해주세요.
<div className={styles.selectWrapper}>
<select
className={styles.sortSelect}
value={sortOption}
onChange={(e) => onSortChange(e.target.value)}
+ aria-label="정렬 옵션"
>
<option value="latest">최신순</option>
<option value="oldest">오래된순</option>
<option value="popular">인기순</option>
</select>📝 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.
| <select | |
| className={styles.sortSelect} | |
| value={sortOption} | |
| onChange={(e) => onSortChange(e.target.value)} | |
| > | |
| <option value="latest">최신순</option> | |
| <option value="oldest">오래된순</option> | |
| <option value="popular">인기순</option> | |
| </select> | |
| <img | |
| src={DropdownArrowIcon} | |
| alt="드롭다운" | |
| className={styles.selectArrow} | |
| /> | |
| </div> | |
| <select | |
| className={styles.sortSelect} | |
| value={sortOption} | |
| onChange={(e) => onSortChange(e.target.value)} | |
| aria-label="정렬 옵션" | |
| > | |
| <option value="latest">최신순</option> | |
| <option value="oldest">오래된순</option> | |
| <option value="popular">인기순</option> | |
| </select> | |
| <img | |
| src={DropdownArrowIcon} | |
| alt="드롭다운" | |
| className={styles.selectArrow} | |
| /> | |
| </div> |
🤖 Prompt for AI Agents
In frontend/src/components/Board/BoardActions.jsx around lines 10 to 24, the
<select> is missing an accessible label; add one by either giving the select a
unique id and rendering a visible or visually-hidden <label
htmlFor="...">Sort</label>, or by adding an appropriate aria-label (e.g.,
aria-label="정렬 기준" or "Sort options") directly on the select; ensure the
onChange and value remain unchanged and update styles if you add a visible label
or apply a sr-only class for a hidden label.
| const handleKeyPress = (e) => { | ||
| if (e.key === 'Enter') { | ||
| handleSearch(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
onKeyPress는 React에서 더 이상 권장되지 않습니다.
onKeyPress 이벤트는 React에서 deprecated되었으며, 향후 제거될 예정입니다. 대신 onKeyDown 또는 onKeyUp을 사용하는 것이 권장됩니다.
다음과 같이 수정하세요:
- const handleKeyPress = (e) => {
+ const handleKeyDown = (e) => {
if (e.key === 'Enter') {
handleSearch();
}
}; onChange={(e) => onSearchChange(e.target.value)}
- onKeyPress={handleKeyPress}
+ onKeyDown={handleKeyDown}
className={styles.searchInput}Also applies to: 25-25
🤖 Prompt for AI Agents
In frontend/src/components/Board/SearchBar.jsx around lines 12-16 (also applies
to line 25), the component uses an onKeyPress handler which is deprecated in
React; replace it with an onKeyDown handler: rename the handler (e.g.,
handleKeyDown), keep the same Enter key check (e.key === 'Enter'), and update
the JSX to use onKeyDown instead of onKeyPress so the Enter key triggers
handleSearch correctly.
| .searchContainer::after { | ||
| content: ''; | ||
| position: absolute; | ||
| inset: var(--search-border); | ||
| border-radius: var(--search-radius); | ||
| background: var(--search-gradient); | ||
| pointer-events: none; | ||
| z-index: -1; | ||
| } |
There was a problem hiding this comment.
z-index: -1은 예상치 못한 렌더링 문제를 일으킬 수 있습니다.
::after 요소에 z-index: -1을 적용하면 부모 요소의 배경이나 상위 컨테이너 뒤로 숨겨질 수 있습니다. 특히 부모 요소에 배경색이나 background 속성이 있는 경우 그라디언트가 보이지 않을 수 있습니다.
더 안전한 대안을 고려하세요:
.searchContainer::after {
content: '';
position: absolute;
inset: var(--search-border);
border-radius: var(--search-radius);
background: var(--search-gradient);
pointer-events: none;
- z-index: -1;
+ z-index: 0;
}
.searchContainer {
position: relative;
+ background: white; /* 또는 원하는 배경색 */
/* ... */
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
frontend/src/components/Board/SearchBar.module.css around lines 44-52: the
::after pseudo-element uses z-index: -1 which can push the gradient behind the
parent or higher-level backgrounds and cause it to be invisible; change the
stacking so the pseudo stays behind the content but above the parent background
by removing the negative z-index (use z-index: 0 or auto) and ensure the parent
has position: relative and a higher stacking context for children (e.g., give
the parent a non-auto z-index or keep it positioned), or alternatively move the
pseudo to ::before and assign appropriate z-index values (pseudo: z-index: 0,
content elements: z-index: 1) so the gradient remains visible without relying on
negative stacking.
| <div className={styles.meta}> | ||
| <img src={ProfileIcon} className={styles.profileIcon} alt="프로필" /> | ||
| <div className={styles.metaInfo}> | ||
| <p className={styles.author}>운영진</p> | ||
| <p className={styles.date}>{getTimeAgo(post.date)}</p> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
하드코딩된 작성자 정보를 post 데이터로 교체하세요.
Line 48에서 작성자가 "운영진"으로 하드코딩되어 있습니다. post.author 속성을 사용하도록 변경이 필요합니다.
- <p className={styles.author}>운영진</p>
+ <p className={styles.author}>{post.author || '운영진'}</p>📝 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.
| <div className={styles.meta}> | |
| <img src={ProfileIcon} className={styles.profileIcon} alt="프로필" /> | |
| <div className={styles.metaInfo}> | |
| <p className={styles.author}>운영진</p> | |
| <p className={styles.date}>{getTimeAgo(post.date)}</p> | |
| </div> | |
| </div> | |
| <div className={styles.meta}> | |
| <img src={ProfileIcon} className={styles.profileIcon} alt="프로필" /> | |
| <div className={styles.metaInfo}> | |
| <p className={styles.author}>{post.author || '운영진'}</p> | |
| <p className={styles.date}>{getTimeAgo(post.date)}</p> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.jsx around lines 45 to 51, the author name is
hardcoded as "운영진"; replace that with the post data by rendering post.author
instead. Update the JSX to use {post.author} (or a safe fallback like
{post.author || '작성자 없음'}) so it handles missing author values, and ensure the
post prop/state is in scope and correctly referenced.
| .content { | ||
| width: var(--width-content); | ||
| margin-bottom: 48px; | ||
| font-weight: 600; | ||
| font-size: 16px; | ||
| line-height: 160%; | ||
| letter-spacing: -3%; | ||
| color: var(--color-text-dark); | ||
| white-space: pre-wrap; | ||
| word-break: break-word; | ||
| overflow-wrap: break-word; | ||
| box-sizing: border-box; | ||
| } |
There was a problem hiding this comment.
잘못된 letter-spacing 값을 수정하세요.
Line 98의 letter-spacing: -3%는 유효하지 않은 CSS입니다. letter-spacing은 백분율 값을 지원하지 않으며, 길이 단위(em, px, rem)를 사용해야 합니다.
다음 diff를 적용하세요:
- letter-spacing: -3%;
+ letter-spacing: -0.03em;📝 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.
| .content { | |
| width: var(--width-content); | |
| margin-bottom: 48px; | |
| font-weight: 600; | |
| font-size: 16px; | |
| line-height: 160%; | |
| letter-spacing: -3%; | |
| color: var(--color-text-dark); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| overflow-wrap: break-word; | |
| box-sizing: border-box; | |
| } | |
| .content { | |
| width: var(--width-content); | |
| margin-bottom: 48px; | |
| font-weight: 600; | |
| font-size: 16px; | |
| line-height: 160%; | |
| letter-spacing: -0.03em; | |
| color: var(--color-text-dark); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| overflow-wrap: break-word; | |
| box-sizing: border-box; | |
| } |
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.module.css around lines 92 to 104, the
declaration letter-spacing: -3% on line 98 is invalid because letter-spacing
does not accept percentage values; replace it with a valid length unit (for
example -0.03em or a pixel value such as -0.5px) and ensure the chosen value
matches design/typography expectations before committing.
| .commentText { | ||
| margin: 0; | ||
| font-weight: 600; | ||
| font-size: 16px; | ||
| line-height: 160%; | ||
| letter-spacing: -3%; | ||
| color: var(--color-text-dark); | ||
| word-break: break-word; | ||
| white-space: pre-wrap; | ||
| overflow-wrap: break-word; | ||
| } |
There was a problem hiding this comment.
잘못된 letter-spacing 값을 수정하세요.
Line 242의 letter-spacing: -3%는 유효하지 않은 CSS입니다. Line 98과 동일한 문제입니다.
다음 diff를 적용하세요:
- letter-spacing: -3%;
+ letter-spacing: -0.03em;📝 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.
| .commentText { | |
| margin: 0; | |
| font-weight: 600; | |
| font-size: 16px; | |
| line-height: 160%; | |
| letter-spacing: -3%; | |
| color: var(--color-text-dark); | |
| word-break: break-word; | |
| white-space: pre-wrap; | |
| overflow-wrap: break-word; | |
| } | |
| .commentText { | |
| margin: 0; | |
| font-weight: 600; | |
| font-size: 16px; | |
| line-height: 160%; | |
| letter-spacing: -0.03em; | |
| color: var(--color-text-dark); | |
| word-break: break-word; | |
| white-space: pre-wrap; | |
| overflow-wrap: break-word; | |
| } |
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.module.css around lines 237 to 247, the rule
uses an invalid value "letter-spacing: -3%"; replace the percent with a valid
unit (match the same fix applied at line 98) such as "letter-spacing: -0.03em"
(or another appropriate em/rem/px value) so the CSS is valid and spacing behaves
as intended.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
frontend/src/pages/PostDetail.jsx (2)
181-183: React 19에서onKeyPress는 지원되지 않습니다.Lines 181-183의 핸들러와 Line 287의 이벤트 prop이 React 19에서 제거된
onKeyPress를 사용하고 있습니다.onKeyDown으로 변경해야 합니다.다음 diff를 적용하세요:
- const handleKeyPress = (e) => { + const handleKeyDown = (e) => { if (e.key === 'Enter' && e.ctrlKey) handleAddComment(); };그리고 Line 287을 수정하세요:
value={commentText} onChange={(e) => setCommentText(e.target.value)} - onKeyPress={handleKeyPress} + onKeyDown={handleKeyDown} />Also applies to: 287-287
237-243: 하드코딩된 작성자 정보를 post 데이터로 교체하세요.Line 240에서 작성자가 "운영진"으로 하드코딩되어 있습니다.
post.author속성을 사용하도록 변경이 필요합니다.다음 diff를 적용하세요:
<div className={styles.metaInfo}> - <p className={styles.author}>운영진</p> + <p className={styles.author}>{post.author || '운영진'}</p> <p className={styles.date}>{getTimeAgo(post.date)}</p> </div>frontend/src/pages/PostDetail.module.css (2)
92-104: 유효하지 않은letter-spacing값을 수정하세요.Line 98의
letter-spacing: -3%는 유효하지 않은 CSS입니다.letter-spacing은 백분율 값을 지원하지 않으며, 길이 단위(em,px,rem)를 사용해야 합니다.다음 diff를 적용하세요:
font-weight: 600; font-size: 16px; line-height: 160%; - letter-spacing: -3%; + letter-spacing: -0.03em; color: var(--color-text-dark);
238-248: 유효하지 않은letter-spacing값을 수정하세요.Line 243의
letter-spacing: -3%는 유효하지 않은 CSS입니다. Line 98과 동일한 문제입니다.다음 diff를 적용하세요:
font-weight: 600; font-size: 16px; line-height: 160%; - letter-spacing: -3%; + letter-spacing: -0.03em; color: var(--color-text-dark);
🧹 Nitpick comments (1)
frontend/src/pages/PostDetail.jsx (1)
122-140: 게시글 수정 UX 개선을 고려하세요.현재
prompt()를 사용하여 수정하고 있는데, 모달 컴포넌트(예: CreatePostModal과 유사한 형태)를 활용하면 더 나은 사용자 경험을 제공할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/src/assets/boardPencil.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
desktop.ini(1 hunks)frontend/src/components/Board/BoardActions.module.css(1 hunks)frontend/src/components/Board/Modal.jsx(1 hunks)frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/pages/Board.jsx(1 hunks)frontend/src/pages/PostDetail.jsx(1 hunks)frontend/src/pages/PostDetail.module.css(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- desktop.ini
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/components/Board/Modal.jsx
- frontend/src/pages/Board.jsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/components/Board/PostItem.jsx (1)
frontend/src/utils/TimeUtils.jsx (2)
getTimeAgo(1-26)getTimeAgo(1-26)
frontend/src/pages/PostDetail.jsx (1)
frontend/src/pages/Board.jsx (3)
posts(9-18)handleLike(54-66)handleBookmark(68-76)
🔇 Additional comments (13)
frontend/src/components/Board/BoardActions.module.css (4)
1-21: 이전 리뷰의--font-family누락 문제가 해결되었습니다.12-13줄에서
--font-family변수가 제대로 정의되어 있으며, 다른 모든 CSS 커스텀 프로퍼티도 일관되게 사용되고 있습니다. CSS Modules의:root스코핑이 올바르게 구현되었습니다.
54-60: 타이포그래피 스타일 일관성이 우수합니다.폰트 관련 속성들(
font-family,font-weight,font-size,line-height,letter-spacing)을 CSS 변수로 중앙 집중식 관리하고 있어, 향후 디자인 토큰 변경 시 유지보수가 용이합니다.Also applies to: 96-101
62-70: 포커스 상태의 접근성 스타일이 적절합니다.
border-color와box-shadow를 통한 명확한 포커스 인디케이터가 구현되어, 키보드 네비게이션 사용자에게 좋은 사용성을 제공합니다.
23-33: Flex 레이아웃이 명확하고 효율적입니다.
justify-content: flex-end로 정렬 옵션과 작성 버튼을 오른쪽으로 배치하는 구조가 직관적이며,gap변수 사용으로 간격 관리가 일관되게 유지되고 있습니다.frontend/src/components/Board/PostItem.jsx (3)
1-11: 컴포넌트 구조와 최적화 전략이 적절합니다.React.memo를 사용한 메모이제이션과 필요한 의존성들이 올바르게 import되어 있습니다.
14-16: state를 통한 네비게이션 동작을 확인하세요.현재
location.state로 post 데이터를 전달하고 있습니다. PostDetail 컴포넌트에서 localStorage fallback을 구현했지만, 직접 URL 접근이나 새로고침 시 localStorage에 해당 게시글이 없으면 데이터가 유실될 수 있습니다.
56-68: 좋아요 기능이 올바르게 구현되었습니다.
post.likeCount를 활용하여 실제 좋아요 수를 표시하고, 조건부 렌더링으로 0일 때는 숨기는 로직이 적절합니다.frontend/src/pages/PostDetail.jsx (4)
1-25: 컴포넌트 구조와 상태 관리가 적절합니다.필요한 hooks와 의존성이 올바르게 import되어 있으며, 상태 관리 구조가 명확합니다.
26-56: 저장소 함수 구현이 견고합니다.try-catch를 통한 에러 처리와 날짜 객체 변환 로직이 적절하게 구현되어 있습니다.
59-75: 게시글 로딩 로직이 적절하게 구현되었습니다.
location.state와 localStorage fallback을 통해 URL 새로고침 시나리오를 처리하고 있습니다. 향후 백엔드 API 연동 시:postId를 활용한 fetch 로직 추가를 권장합니다.
281-289: 댓글 입력 UI가 적절하게 구현되었습니다.textarea의 placeholder, value 바인딩, onChange 핸들러가 올바르게 설정되어 있습니다. (onKeyPress 이슈는 별도 코멘트 참고)
frontend/src/pages/PostDetail.module.css (2)
1-16: CSS 변수 활용이 우수합니다.색상, 크기, 간격을 CSS 변수로 정의하여 일관성과 유지보수성을 향상시켰습니다.
266-321: 반응형 디자인이 적절하게 구현되었습니다.모바일(400px), 태블릿(768px), 데스크톱 환경에 대한 적절한 브레이크포인트와 스타일 조정이 구현되어 있습니다.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
frontend/src/pages/PostDetail.jsx (2)
236-243: 게시글 작성자 정보가 여전히 하드코딩되어 있습니다.Line 240에서 작성자가 "운영진"으로 고정되어 있습니다. 이전 리뷰에서 지적되었으나 아직 수정되지 않았습니다.
post.author속성을 사용하도록 변경하세요.- <p className={styles.author}>운영진</p> + <p className={styles.author}>{post.author || '운영진'}</p>
181-183: 키보드 핸들러가 정의되었으나 사용되지 않고 있습니다.
handleKeyPress함수가 정의되어 있지만 textarea(Line 284-289)에 연결되지 않았습니다. 또한 React 19에서는onKeyPress가 제거되었으므로onKeyDown을 사용해야 합니다.다음과 같이 수정하세요:
1단계: 함수명 변경
- const handleKeyPress = (e) => { + const handleKeyDown = (e) => { if (e.key === 'Enter' && e.ctrlKey) handleAddComment(); };2단계: textarea에 핸들러 연결 (Line 284 부근)
<textarea className={styles.textarea} placeholder="댓글을 입력해주세요. 허위사실, 욕설 등을 포함한 댓글은 별도의 안내 없이 삭제될 수 있어요." value={commentText} onChange={(e) => setCommentText(e.target.value)} + onKeyDown={handleKeyDown} />
🧹 Nitpick comments (2)
frontend/src/pages/PostDetail.jsx (2)
122-140: 게시글 수정 시prompt()대신 모달 컴포넌트 사용을 권장합니다.현재 구현은 동작하지만,
prompt()는 다음과 같은 제약이 있습니다:
- 여러 줄 콘텐츠 편집 시 사용자 경험이 좋지 않음
- 실시간 유효성 검사 불가
- 모바일 환경에서 사용성 저하
게시글 작성 시 사용하는
CreatePostModal과 유사한 편집 모달을 도입하는 것을 고려해보세요.
158-168: 댓글 수정 시에도prompt()대신 인라인 편집이나 모달을 권장합니다.게시글 수정과 동일한 이슈입니다. 댓글 수정 시 더 나은 사용자 경험을 제공하기 위해 인라인 편집 UI를 고려해보세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/pages/PostDetail.jsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Board/PostItem.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PostDetail.jsx (1)
frontend/src/pages/Board.jsx (3)
posts(9-19)handleLike(55-67)handleBookmark(69-77)
🔇 Additional comments (5)
frontend/src/pages/PostDetail.jsx (5)
27-56: 스토리지 헬퍼 함수 구현이 적절합니다.에러 처리와 Date 객체 복원 로직이 올바르게 구현되어 있습니다.
59-75: 게시글 로딩 로직이 올바르게 구현되었습니다.
location.state를 우선 사용하고 없을 경우 localStorage에서 조회하는 방식으로 URL 새로고침 시에도 데이터가 유지됩니다.
78-109: 좋아요와 북마크 토글 로직이 정확합니다.localStorage와 컴포넌트 상태 간 동기화가 올바르게 처리되고 있습니다.
248-275: 좋아요와 북마크 카운트가 동적으로 표시됩니다.이전 리뷰에서 지적된 하드코딩된 북마크 카운트 문제가 수정되었습니다.
post.bookmarkCount와post.likeCount를 사용하여 실제 값을 표시하고 있습니다.
298-349: 댓글 렌더링 로직이 올바르게 구현되었습니다.댓글 목록, 수정/삭제 메뉴, 빈 상태 처리가 모두 적절하게 구현되어 있습니다.
| const handleAddComment = () => { | ||
| if (!commentText.trim()) return; | ||
| const newComment = { | ||
| id: Date.now(), | ||
| author: '사용자', | ||
| text: commentText, | ||
| date: new Date(), | ||
| }; | ||
| const updatedComments = [...comments, newComment]; | ||
| setComments(updatedComments); | ||
| saveCommentsToStorage(post.id, updatedComments); | ||
| setCommentText(''); | ||
| }; |
There was a problem hiding this comment.
댓글 작성자 정보가 하드코딩되어 있습니다.
Line 147에서 댓글 작성자가 '사용자'로 고정되어 있습니다. 게시글 작성자 문제(Line 240)와 동일한 이슈입니다.
실제 사용자 인증 정보나 컨텍스트에서 작성자 정보를 가져오도록 변경하세요.
const newComment = {
id: Date.now(),
- author: '사용자',
+ author: currentUser?.name || '익명',
text: commentText,
date: new Date(),
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.jsx around lines 143 to 155, the new comment
handler hardcodes the author as '사용자'; replace this with the real current user
from authentication or app context (e.g., currentUser from AuthContext or a
currentUser prop). Update newComment.author (and include an id if needed) to
come from that user object, fallback to a sensible placeholder only if no user
is available, and ensure any required imports/useContext calls are added and
tested so comments persist with correct author info.
This reverts commit 6f99148.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
frontend/src/pages/PostDetail.jsx (4)
147-147: 댓글 작성자를 실제 사용자 정보로 교체하세요.Line 147에서 댓글 작성자가 '사용자'로 하드코딩되어 있습니다. 인증 컨텍스트나 사용자 상태에서 실제 사용자 정보를 가져와야 합니다.
다음과 같이 수정할 수 있습니다:
const newComment = { id: Date.now(), - author: '사용자', + author: currentUser?.name || '익명', text: commentText, date: new Date(), };인증 컨텍스트를 사용하는 경우 컴포넌트 상단에 추가:
const { currentUser } = useAuth(); // 또는 프로젝트의 인증 훅
181-183: React 19에서onKeyPress는 더 이상 지원되지 않습니다.Line 181-183의
handleKeyPress함수와 Line 287의onKeyPress이벤트는 React 19에서 제거되었습니다.onKeyDown으로 변경해야 합니다.다음 diff를 적용하세요:
- const handleKeyPress = (e) => { + const handleKeyDown = (e) => { if (e.key === 'Enter' && e.ctrlKey) handleAddComment(); };그리고 Line 287을 수정하세요:
onChange={(e) => setCommentText(e.target.value)} - onKeyPress={handleKeyPress} + onKeyDown={handleKeyDown} />Also applies to: 287-287
240-240: 하드코딩된 작성자 정보를 게시글 데이터로 교체하세요.Line 240에서 작성자가 "운영진"으로 하드코딩되어 있습니다.
post.author속성을 사용하도록 변경이 필요합니다.다음 diff를 적용하세요:
- <p className={styles.author}>운영진</p> + <p className={styles.author}>{post.author || '익명'}</p>
258-258: 북마크 카운트 표시가 좋아요 로직과 일관성이 없습니다.Line 258에서 북마크된 경우 항상 "1"을 표시하고 있는 반면, Line 269-271의 좋아요는
post.likeCount를 동적으로 표시합니다. 북마크도 실제 카운트를 표시하거나, 카운트 표시를 제거하여 일관성을 유지하세요.다음과 같이 수정할 수 있습니다:
<img src={post.isBookmarked ? BookmarkFilledIcon : BookmarkIcon} alt="북마크" /> - {post.isBookmarked && <span className={styles.count}>1</span>} + {post.bookmarkCount > 0 && ( + <span className={styles.count}>{post.bookmarkCount}</span> + )}또는 카운트 표시를 제거:
<img src={post.isBookmarked ? BookmarkFilledIcon : BookmarkIcon} alt="북마크" /> - {post.isBookmarked && <span className={styles.count}>1</span>}
🧹 Nitpick comments (1)
frontend/src/pages/PostDetail.jsx (1)
113-113: 사용자 경험 개선을 위해 커스텀 모달을 고려하세요.Line 113의
window.confirm과 Line 124, 126의prompt는 네이티브 브라우저 다이얼로그로 스타일링이 불가능하고 사용자 경험이 제한적입니다. 프로젝트에 이미 Modal 컴포넌트가 있으므로, 게시글 수정/삭제 시에도 일관된 UI를 제공하는 커스텀 모달을 사용하는 것을 권장합니다.Also applies to: 124-127
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
frontend/src/components/Board/PostItem.jsx(1 hunks)frontend/src/pages/PostDetail.jsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/src/components/Board/PostItem.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PostDetail.jsx (3)
frontend/src/components/Board/PostItem.jsx (1)
navigate(12-12)frontend/src/pages/Board.jsx (3)
posts(9-19)handleLike(55-67)handleBookmark(69-77)frontend/src/components/Board/SearchBar.jsx (1)
handleKeyPress(12-16)
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (5)
frontend/src/pages/PostDetail.jsx (5)
96-109: 동일한 경합 조건이handleBookmark에도 존재합니다.
handleLike와 동일한 경합 조건 문제가 있습니다. 빠른 연속 클릭 시 업데이트가 손실될 수 있습니다.
143-155: 댓글 작성자 정보가 여전히 하드코딩되어 있습니다.Line 147에서 댓글 작성자가 '사용자'로 고정되어 있습니다. 이전 리뷰에서도 지적되었으나 아직 수정되지 않았습니다.
실제 사용자 인증 컨텍스트나 props에서 작성자 정보를 가져와야 합니다:
+ // 컴포넌트 상단에서 사용자 컨텍스트 가져오기 + // const { currentUser } = useAuth(); // 예시 + const handleAddComment = () => { if (!commentText.trim()) return; const newComment = { id: Date.now(), - author: '사용자', + author: currentUser?.name || '익명', text: commentText, date: new Date(), };
181-183: React 19에서onKeyPress는 제거되었습니다 - 즉시 수정 필요.이 함수가 Line 288에서
onKeyPress이벤트와 함께 사용되고 있는데,onKeyPress는 React 19에서 완전히 제거되었습니다. 이전 리뷰에서도 지적되었으나 아직 수정되지 않았습니다.다음과 같이 수정하세요:
- const handleKeyPress = (e) => { + const handleKeyDown = (e) => { if (e.key === 'Enter' && e.ctrlKey) handleAddComment(); };그리고 Line 288도 함께 수정:
- onKeyPress={handleKeyPress} + onKeyDown={handleKeyDown}
238-244: 게시글 작성자가 여전히 하드코딩되어 있습니다.Line 241에서 작성자가 "운영진"으로 고정되어 있습니다. 이전 리뷰에서도 지적되었으나 수정되지 않았습니다.
post.author속성을 사용하도록 변경하세요:- <p className={styles.author}>운영진</p> + <p className={styles.author}>{post.author || '운영진'}</p>
250-260: 북마크 카운트가 여전히 하드코딩되어 있습니다.Line 259에서 북마크된 경우 항상 "1"을 표시하고 있습니다. 이전 리뷰에서 "addressed"로 표시되었으나 코드에는 여전히 하드코딩된 값이 남아있습니다.
실제 북마크 수를 표시하도록 수정하세요:
- {post.isBookmarked && <span className={styles.count}>1</span>} + {post.bookmarkCount > 0 && ( + <span className={styles.count}>{post.bookmarkCount}</span> + )}
🧹 Nitpick comments (3)
frontend/src/pages/PostDetail.jsx (3)
27-56: localStorage 할당량 초과 에러 처리를 고려하세요.
savePostsToStorage와saveCommentsToStorage함수에서 localStorage의 용량 제한을 초과할 경우QuotaExceededError가 발생할 수 있습니다. 많은 게시글이나 댓글이 쌓일 경우 사용자에게 적절한 안내 없이 저장이 실패할 수 있습니다.다음과 같이 에러 처리를 추가할 수 있습니다:
const savePostsToStorage = (posts) => { - localStorage.setItem('boardPosts', JSON.stringify(posts)); + try { + localStorage.setItem('boardPosts', JSON.stringify(posts)); + } catch (error) { + if (error.name === 'QuotaExceededError') { + console.error('localStorage 용량이 부족합니다.'); + // 사용자에게 알림 표시 또는 오래된 데이터 정리 + } + throw error; + } };
122-140:prompt()를 모달 컴포넌트로 교체하는 것을 권장합니다.
prompt()를 사용한 게시글 수정은 사용자 경험이 제한적입니다. 스타일링이 불가능하고, 여러 줄 입력 시 불편하며, 모바일 환경에서 특히 사용성이 떨어집니다.프로젝트에 이미
CreatePostModal컴포넌트가 있으므로, 이를 수정 모드로 재사용하거나 유사한 수정 전용 모달을 구현하는 것을 권장합니다.
157-168: 댓글 수정에도 모달 UI를 사용하는 것을 권장합니다.게시글 수정과 마찬가지로
prompt()는 제한적인 사용자 경험을 제공합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/pages/PostDetail.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PostDetail.jsx (1)
frontend/src/pages/Board.jsx (3)
posts(9-19)handleLike(55-67)handleBookmark(69-77)
🔇 Additional comments (4)
frontend/src/pages/PostDetail.jsx (4)
58-75: 데이터 로딩 로직이 잘 구현되었습니다.
location.state에서 게시글을 먼저 확인하고, 없으면 localStorage에서 ID로 검색하는 fallback 메커니즘이 적절합니다. 페이지 새로고침과 직접 URL 접근 시나리오를 모두 처리합니다.
207-234: 메뉴 버튼의 접근성 개선이 잘 적용되었습니다.Line 210에
aria-label="게시글 메뉴 열기"속성이 추가되어 스크린 리더 사용자가 버튼의 용도를 이해할 수 있습니다.
314-342: 댓글 메뉴 버튼의 접근성도 잘 구현되었습니다.Line 318에
aria-label="댓글 메뉴 열기"속성이 적절히 추가되었습니다.
78-94: 단일 탭에서의 빠른 연속 클릭은 경합 조건을 야기하지 않습니다.JavaScript는 단일 이벤트 루프 내에서 단일 스레드로 동작하므로, 한 탭에서 빠르게 연속된 클릭으로 인한 경합 조건은 발생하지 않습니다. 각
handleLike호출은 순차적으로 완료됩니다.localStorage 경합 조건은 여러 브라우저 탭/창이 동시에 동일한 데이터를 읽고 수정할 때만 발생합니다. 현재 코드에는 탭 간 동기화 메커니즘(저장소 이벤트 리스너, 상태 공유 등)이 없으므로, 이는 설계된 시나리오가 아닙니다.
제안된 낙관적 업데이트는 UX 개선에는 유용하지만, 실제 다중 탭 localStorage 경합 조건을 해결하지는 못합니다. 다중 탭 지원이 필요하다면 버전 관리, 저장소 이벤트, 또는 IndexedDB 같은 다른 해결책이 필요합니다.
Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
frontend/src/pages/PostDetail.jsx (2)
151-163: 댓글 작성자 정보가 여전히 하드코딩되어 있습니다.Line 155의 댓글 작성자가 '사용자'로 고정되어 있는 문제가 이전 리뷰에서 지적되었으나 아직 수정되지 않았습니다. 실제 사용자 인증 컨텍스트에서 작성자 정보를 가져와야 합니다.
AuthContext 또는 유사한 사용자 컨텍스트를 추가하고 다음과 같이 수정하세요:
+import { useAuth } from '../contexts/AuthContext'; // 또는 적절한 경로 + const PostDetail = () => { + const { currentUser } = useAuth(); // ... 기존 코드 const handleAddComment = () => { if (!commentText.trim()) return; const newComment = { id: Date.now(), - author: '사용자', + author: currentUser?.name || '익명', text: commentText, date: new Date(), };
246-252: 게시글 작성자 정보가 여전히 하드코딩되어 있습니다.Line 249의 작성자가 "운영진"으로 고정되어 있는 문제가 이전 리뷰에서 지적되었으나 아직 수정되지 않았습니다.
post.author속성을 사용해야 합니다.다음 diff를 적용하세요:
<div className={styles.meta}> <img src={ProfileIcon} className={styles.profileIcon} alt="프로필" /> <div className={styles.metaInfo}> - <p className={styles.author}>운영진</p> + <p className={styles.author}>{post.author || '익명'}</p> <p className={styles.date}>{getTimeAgo(post.date)}</p> </div> </div>
🧹 Nitpick comments (3)
frontend/src/pages/PostDetail.jsx (3)
86-117: 좋아요/북마크 핸들러의 중복 코드를 리팩토링하는 것을 고려하세요.
handleLike와handleBookmark는 거의 동일한 패턴을 따릅니다. 공통 로직을 추출하여 코드 중복을 줄일 수 있습니다.다음과 같이 공통 함수를 추출할 수 있습니다:
const updatePostField = (updateFn) => { const allPosts = getPostsFromStorage(); let updatedPost = null; const updatedPosts = allPosts.map((p) => { if (p.id === post.id) { updatedPost = updateFn(p); return updatedPost; } return p; }); savePostsToStorage(updatedPosts); setPost(updatedPost); }; const handleLike = () => { updatePostField((p) => ({ ...p, isLiked: !p.isLiked, likeCount: p.isLiked ? p.likeCount - 1 : p.likeCount + 1, })); }; const handleBookmark = () => { updatePostField((p) => ({ ...p, isBookmarked: !p.isBookmarked })); };
130-148: 게시글 수정 UI를 모달 컴포넌트로 개선하는 것을 권장합니다.
prompt()를 사용한 수정 UI는 기능적으로 동작하지만 사용자 경험이 좋지 않습니다. 프로젝트에 이미CreatePostModal이 있으므로, 유사한 수정 모달을 구현하는 것을 권장합니다.다음과 같이 수정 모달 컴포넌트를 추가할 수 있습니다:
// EditPostModal 컴포넌트 사용 const [showEditModal, setShowEditModal] = useState(false); const handleUpdate = () => { setShowMenu(false); setShowEditModal(true); }; // 렌더링 부분에 추가 {showEditModal && ( <EditPostModal post={post} onClose={() => setShowEditModal(false)} onSave={(updatedData) => { // 저장 로직 }} /> )}
189-191: 키보드 핸들러에 이벤트 타겟 검증을 추가하는 것을 고려하세요.현재
handleKeyDown은 textarea에만 연결되어 있지만, 향후 다른 요소에 추가될 경우를 대비해 이벤트 타겟을 검증하는 것이 좋습니다.const handleKeyDown = (e) => { - if (e.key === 'Enter' && e.ctrlKey) handleAddComment(); + if (e.key === 'Enter' && e.ctrlKey && e.target.tagName === 'TEXTAREA') { + handleAddComment(); + } };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
frontend/src/pages/PostDetail.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PostDetail.jsx (1)
frontend/src/pages/Board.jsx (3)
posts(9-19)handleLike(55-67)handleBookmark(69-77)
🔇 Additional comments (1)
frontend/src/pages/PostDetail.jsx (1)
255-361: 댓글 섹션 구현이 잘 되어 있습니다.댓글 입력, 북마크/좋아요 액션, 접근성 레이블이 모두 적절하게 구현되어 있습니다. 이전 리뷰에서 지적된 북마크 카운트 하드코딩 문제도 해결되었습니다.
| const saveCommentsToStorage = (postId, comments) => { | ||
| localStorage.setItem(`comments_${postId}`, JSON.stringify(comments)); | ||
| }; |
There was a problem hiding this comment.
댓글 저장 함수에도 에러 처리를 추가하세요.
saveCommentsToStorage는 savePostsToStorage와 달리 에러 처리가 없습니다. localStorage 용량 초과나 파싱 오류 시 적절한 처리가 필요합니다.
다음 diff를 적용하세요:
const saveCommentsToStorage = (postId, comments) => {
- localStorage.setItem(`comments_${postId}`, JSON.stringify(comments));
+ try {
+ localStorage.setItem(`comments_${postId}`, JSON.stringify(comments));
+ } catch (error) {
+ if (error.name === 'QuotaExceededError') {
+ console.error('localStorage 용량이 부족합니다.');
+ }
+ throw error;
+ }
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const saveCommentsToStorage = (postId, comments) => { | |
| localStorage.setItem(`comments_${postId}`, JSON.stringify(comments)); | |
| }; | |
| const saveCommentsToStorage = (postId, comments) => { | |
| try { | |
| localStorage.setItem(`comments_${postId}`, JSON.stringify(comments)); | |
| } catch (error) { | |
| if (error.name === 'QuotaExceededError') { | |
| console.error('localStorage 용량이 부족합니다.'); | |
| } | |
| throw error; | |
| } | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/pages/PostDetail.jsx around lines 62 to 64, the
saveCommentsToStorage function lacks error handling; wrap the
localStorage.setItem and JSON.stringify call in a try-catch, handle JSON
stringify failures and localStorage quota exceptions by catching the error,
logging a clear message (including postId and error) and optionally falling back
(e.g., silencing gracefully or trimming data) consistent with savePostsToStorage
behavior so the app doesn't crash on storage errors.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
frontend/src/utils/TimeUtils.js (2)
1-5: 입력값 검증 및 예외 처리 추가 권장
createdAt파라미터에 대한 유효성 검사가 없어 다음과 같은 문제가 발생할 수 있습니다:
null또는undefined입력 시 런타임 에러 발생- 잘못된 날짜 형식 입력 시
Invalid Date처리 미흡- 미래 날짜 입력 시 부정확한 결과 반환 (음수 차이가 "방금전"으로 표시됨)
다음과 같이 예외 처리를 추가하는 것을 고려해보세요:
export const getTimeAgo = (createdAt) => { + if (!createdAt) { + return '알 수 없음'; + } + const now = new Date(); const created = new Date(createdAt); + + if (isNaN(created.getTime())) { + return '알 수 없음'; + } + const diffInMs = now - created; + + // 미래 날짜 처리 + if (diffInMs < 0) { + return '방금전'; + }
10-11: 개월/연도 계산의 근사치 사용30일을 한 달로 계산하는 방식은 근사치입니다. 실제 월은 28-31일로 다양하지만, 게시판의 상대 시간 표시 용도로는 충분히 허용 가능한 수준입니다.
더 정확한 계산이 필요한 경우
date-fns나dayjs같은 라이브러리 사용을 고려할 수 있습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.gitignore(1 hunks)frontend/src/utils/TimeUtils.js(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🔇 Additional comments (1)
frontend/src/utils/TimeUtils.js (1)
13-25: 조건문 로직 정상 동작시간 단위별 임계값 설정과 한글 문자열 포맷이 적절하게 구현되어 있습니다. 로직 흐름이 명확하고 의도대로 동작합니다.
1) 작업한 이슈번호
SISC-149
2) 변경 요약 (What & Why)
3) 스크린샷/동영상 (UI 변경 시)
4) 상세 변경사항 (전부 다)
라우팅/페이지:
pages/Board/Board.jsx- 게시판 메인 페이지 컴포넌트 추가컴포넌트:
Board컴포넌트: 게시글 목록 표시, 정렬 기능, 검색 UICreatePostModal컴포넌트: 게시글 작성 모달 (제목, 내용, 접근권한 선택)PostItem컴포넌트: 개별 게시글 카드 컴포넌트상태관리:
useState로 모달 열기/닫기, 정렬 옵션, 게시글 목록 상태 관리API 호출:
스타일:
#F8F9FA배경)기타:
5) 참고사항
*.module.css형태로 네이밍pages/Board/,components/Board/디렉토리 구조Summary by CodeRabbit