-
Notifications
You must be signed in to change notification settings - Fork 1
[25.03.10 / TASK-121] Feature - 공지사항 기능 구현 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough이번 PR은 여러 영역에 걸쳐 기능이 확장된 변경사항을 포함합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as 사용자
participant H as Header 컴포넌트
participant M as useModal / Modal
participant API as API (notiList)
U->>H: 공지사항 버튼 클릭
H->>M: ModalOpen 호출하여 모달 열기
M->>M: 모달 렌더링 및 useQuery 호출
M->>API: 공지사항 데이터 요청 (notiList)
API-->>M: 공지사항 데이터 반환
M->>U: 데이터 표시 (모달 내 공지사항)
U->>M: Escape 키 또는 외부 클릭
M->>M: 모달 닫힘 처리
Suggested labels
Suggested reviewers
Poem
Tip ⚡🧪 Multi-step agentic review comment chat (experimental)
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/apis/notice.request.ts (1)
1-6: 공지사항 API 요청 함수 구현이 잘 되었습니다공지사항 목록을 가져오는 API 함수가 간결하고 명확하게 구현되어 있습니다. 적절한 타입 지정과 함께 PATHS 상수를 활용하여 엔드포인트를 관리하는 방식이 좋습니다.
한 가지 제안사항으로, 에러 처리 로직을 추가하는 것을 고려해볼 수 있습니다. API 요청 실패 시 적절한 오류 메시지나 대체 UI를 제공할 수 있도록 하면 사용자 경험이 향상될 것입니다.
src/components/auth-required/notice/Modal.tsx (3)
16-16: 오타 수정이 필요합니다.클래스명에 오타가 있습니다.
- <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3">공지사항</h2> + <h2 className="text-TEXT-MAIN items-center gap-3 text-T3">공지사항</h2>
10-11: 로딩 및 에러 상태 처리가 필요합니다.API 요청 중 로딩 상태와 오류 상태를 처리하는 코드가 없습니다. 사용자 경험 향상을 위해 이러한 상태를 처리하는 것이 좋습니다.
- const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, error } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); return ( <div className="w-[800px] h-[500px] overflow-auto flex flex-col gap-5 p-10 rounded-md bg-BG-SUB"> <div className="flex items-center justify-between"> <h2 className="text-TEXT-MAIN items-center gap-3 text-T3">공지사항</h2> <Icon name="Close" onClick={close} className="cursor-pointer" /> </div> + {isLoading && ( + <div className="flex justify-center items-center h-[400px]"> + <p className="text-TEXT-ALT">로딩 중...</p> + </div> + )} + + {error && ( + <div className="flex justify-center items-center h-[400px]"> + <p className="text-TEXT-ALT">데이터를 불러오는 중 오류가 발생했습니다.</p> + </div> + )} - {data?.posts?.map(({ content, created_at, id, title }) => ( + {!isLoading && !error && data?.posts?.map(({ content, created_at, id, title }) => (
24-26: 날짜 형식 개선이 필요합니다.현재 날짜는
T를 기준으로 분할하여 처리하고 있습니다. 더 견고한 날짜 형식 처리를 위해 Date 객체나 날짜 라이브러리 사용을 권장합니다.- <h4 className="text-TEXT-ALT text-T5"> - {created_at.split('T')[0]} - </h4> + <h4 className="text-TEXT-ALT text-T5"> + {new Date(created_at).toLocaleDateString('ko-KR', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + </h4>또는 더 다양한 형식 지원이 필요하다면 date-fns 같은 라이브러리를 고려하세요:
+ import { format } from 'date-fns'; + import { ko } from 'date-fns/locale'; - <h4 className="text-TEXT-ALT text-T5"> - {created_at.split('T')[0]} - </h4> + <h4 className="text-TEXT-ALT text-T5"> + {format(new Date(created_at), 'yyyy년 MM월 dd일', { locale: ko })} + </h4>src/components/auth-required/header/index.tsx (1)
15-16: Import 순서 수정 필요import 순서가 ESLint 규칙에 맞지 않습니다.
@/hooks/useModal과 관련 import는 상대 경로 import인./Section이전에 와야 합니다.- import { defaultStyle, Section, textStyle } from './Section'; - import { useModal } from '@/hooks/useModal'; - import { Modal } from '../notice/Modal'; + import { useModal } from '@/hooks/useModal'; + import { Modal } from '../notice/Modal'; + import { defaultStyle, Section, textStyle } from './Section';🧰 Tools
🪛 ESLint
[error] 15-15:
@/hooks/useModalimport should occur before import of./Section(import/order)
src/components/auth-required/notice/index.tsx (1)
29-53: 접근성 개선 제안알림 배너의 구현이 잘 되었습니다. UI 접근성을 높이기 위해 ARIA 속성 추가를 고려해 보세요.
<div className={`transition-all shrink-0 duration-300 flex items-center justify-center gap-2 w-full overflow-hidden bg-BORDER-SUB ${show ? 'h-[50px]' : 'h-[0px]'}`} + aria-hidden={!show} + role="alert" >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/components/common/Icon/icons/Close.svgis excluded by!**/*.svg
📒 Files selected for processing (15)
package.json(1 hunks)src/apis/index.ts(1 hunks)src/apis/notice.request.ts(1 hunks)src/app/(with-tracker)/(auth-required)/layout.tsx(2 hunks)src/app/layout.tsx(2 hunks)src/components/auth-required/header/index.tsx(3 hunks)src/components/auth-required/notice/Modal.tsx(1 hunks)src/components/auth-required/notice/index.tsx(1 hunks)src/components/common/Icon/icons/index.ts(1 hunks)src/components/common/ModalProvider.tsx(1 hunks)src/components/common/index.ts(1 hunks)src/constants/paths.constant.ts(1 hunks)src/hooks/useModal.tsx(1 hunks)src/types/index.ts(1 hunks)src/types/notice.type.ts(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/auth-required/notice/Modal.tsx
[error] 30-30: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🪛 ESLint
src/components/auth-required/header/index.tsx
[error] 15-15: @/hooks/useModal import should occur before import of ./Section
(import/order)
🔇 Additional comments (14)
src/components/common/Icon/icons/index.ts (1)
7-7: 아이콘 추가가 잘 이루어졌습니다.이 변경은
Close아이콘을 내보내도록 추가했으며, 공지사항 모달에서 사용될 것으로 보입니다. 아이콘 네이밍이 명확하고 기존 패턴을 잘 따르고 있습니다.src/types/index.ts (1)
4-4: 타입 내보내기가 잘 구현되었습니다.공지사항 관련 타입(
notice.type)을 내보내기 추가함으로써 애플리케이션 전체에서 이 타입들을 사용할 수 있게 되었습니다. 이는NotiListDto타입을 활용하는 API 호출 및 UI 컴포넌트와 잘 통합됩니다.src/components/common/index.ts (1)
2-2: 모달 프로바이더 내보내기가 적절히 추가되었습니다.
ModalProvider를 내보내기에 추가함으로써 애플리케이션 전체에서 모달 기능을 사용할 수 있게 되었습니다. 이 변경은 공지사항 기능에 필요한 모달 관리를 위해 적절하게 구현되었습니다.src/apis/index.ts (1)
3-3: 공지사항 API 내보내기가 잘 구현되었습니다.
notice.request모듈을 내보내기에 추가함으로써notiList함수가 애플리케이션 전체에서 접근 가능하게 되었습니다. 이 API 함수는 공지사항 데이터를 가져오는 데 사용되며, 다른 컴포넌트들과 잘 통합되어 있습니다.package.json (1)
34-35: 상태 관리를 위한 zustand 라이브러리 추가zustand 라이브러리가 추가되었네요. 이 라이브러리는 React 애플리케이션에서 상태 관리를 간단하게 할 수 있게 해주는 도구로, 공지사항 모달 상태 관리에 사용될 것으로 보입니다.
src/types/notice.type.ts (1)
1-8: 알림 데이터 타입 정의가 잘 되었습니다공지사항 데이터를 위한 타입 정의가 명확하게 되어 있습니다. 각 게시물(post)에 필요한 id, title, content, created_at 필드가 모두 포함되어 있어 타입 안정성을 확보할 수 있습니다.
src/constants/paths.constant.ts (1)
4-4: 공지사항 경로 상수가 적절히 추가되었습니다공지사항 데이터를 가져오기 위한 경로 상수 'NOTIS'가 적절하게 추가되었습니다. 경로를 상수로 관리하는 것은 코드 유지보수성을 높이는 좋은 방법입니다.
src/hooks/useModal.tsx (1)
1-17: 잘 구현된 모달 관리 훅입니다!Zustand를 활용한 모달 상태 관리 구현이 깔끔하고 효율적입니다. 다양한 컴포넌트에서 모달을 쉽게 열고 닫을 수 있는 직관적인 API를 제공합니다.
한 가지 제안으로, 타입 안전성을 더 높이기 위해 Modal 타입을 더 구체적으로 정의할 수 있습니다:
- interface ModalType { - Modal?: React.ReactNode; - // eslint-disable-next-line no-unused-vars - open: (Modal: React.ReactNode) => void; - close: () => void; - } + interface ModalType { + Modal?: React.ReactNode; + // eslint-disable-next-line no-unused-vars + open: (Modal: React.ReactNode) => void; + close: () => void; + }src/app/layout.tsx (1)
9-13: 모달 프로바이더가 적절하게 통합되었습니다.레이아웃에 ModalProvider를 추가한 방식이 깔끔하게 구현되었습니다. 프로바이더 계층 구조 내에 올바르게 배치되어 있어 애플리케이션 전반에서 모달 기능을 사용할 수 있습니다.
Also applies to: 48-48
src/components/auth-required/header/index.tsx (1)
35-35: Modal hook 적용 잘 되었습니다.useModal hook을 활용한 Modal 상태 관리가 잘 구현되었습니다.
src/components/auth-required/notice/index.tsx (1)
10-11: 상수 정의 적절히 되었습니다재사용 가능한 시간 관련 상수가 잘 정의되었습니다.
src/app/(with-tracker)/(auth-required)/layout.tsx (3)
5-7: import 정리가 잘 되었습니다새로운 기능을 위한 import 추가가 잘 되었습니다.
21-24: notiList prefetch 구현이 좋습니다공지사항 데이터를 미리 가져오는 prefetch 구현이 적절하게 되었습니다. 사용자 경험 향상에 도움이 됩니다.
27-35: 레이아웃 구조 변경이 잘 이루어졌습니다HydrationBoundary와 함께 Notice 컴포넌트를 추가하는 레이아웃 구조 변경이 깔끔하게 이루어졌습니다. 사용자가 항상 최신 공지를 볼 수 있도록 적절한 위치에 배치되었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
src/components/auth-required/notice/Modal.tsx (1)
37-39:⚠️ Potential issue중복된 닫는 태그를 제거해 주세요.
파일 끝부분에 불필요한 중복 닫는 태그들이 있습니다. 이 태그들은 삭제되어야 합니다.
); }; - ); - }; -
♻️ Duplicate comments (1)
src/components/auth-required/notice/Modal.tsx (1)
28-31:⚠️ Potential issue보안 취약점: HTML 콘텐츠 삽입 시 XSS 공격 위험이 있습니다.
dangerouslySetInnerHTML을 사용할 때 HTML 콘텐츠를 삽입하기 전에 반드시 적절한 살균(sanitize) 처리가 필요합니다. 현재 구현은 XSS(Cross-Site Scripting) 공격에 취약합니다.다음과 같이 DOMPurify 같은 라이브러리를 사용하여 HTML을 살균하세요:
+ import DOMPurify from 'dompurify'; <div className="text-TEXT-MAIN text-ST5" - dangerouslySetInnerHTML={{ __html: content }} + dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} ></div>필요한 패키지를 설치하려면:
npm install dompurify npm install --save-dev @types/dompurify
🧹 Nitpick comments (3)
src/components/auth-required/notice/Modal.tsx (3)
16-16: 오타 수정: 'items-cenetr'를 'items-center'로 수정하세요.className 속성에 오타가 있습니다.
- <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3">공지사항</h2> + <h2 className="text-TEXT-MAIN items-center gap-3 text-T3">공지사항</h2>
20-33: 데이터 로딩 및 오류 상태 처리를 추가하세요.현재 코드는 데이터가 로드되는 동안의 상태나 오류가 발생했을 때의 처리가 없습니다. 더 나은 사용자 경험을 위해 로딩 및 오류 상태에 대한 UI 처리를 추가하는 것이 좋습니다.
- {data?.posts?.map(({ content, created_at, id, title }) => ( + {data?.posts?.length === 0 ? ( + <div className="text-TEXT-MAIN text-center">공지사항이 없습니다.</div> + ) : data?.posts?.map(({ content, created_at, id, title }) => (또한 로딩 상태 처리를 위해 다음과 같이 변경할 수 있습니다:
- const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, isError } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); ... + {isLoading && <div className="text-TEXT-MAIN text-center">로딩 중...</div>} + {isError && <div className="text-TEXT-MAIN text-center">오류가 발생했습니다.</div>} {data?.posts?.length === 0 ? ( <div className="text-TEXT-MAIN text-center">공지사항이 없습니다.</div> ) : data?.posts?.map(({ content, created_at, id, title }) => (🧰 Tools
🪛 Biome (1.9.4)
[error] 32-32: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
24-26: 날짜 포맷팅을 위한 별도 함수 또는 라이브러리 사용을 고려하세요.현재 날짜 포맷팅을 위해 문자열 split을 사용하고 있습니다. 이 방식은 간단하지만, 더 복잡한 날짜 포맷팅이 필요하거나 국제화가 필요한 경우 확장성이 떨어집니다. date-fns 또는 dayjs와 같은 라이브러리를 사용하거나 별도의 포맷팅 함수를 만드는 것이 좋습니다.
+ import { format } from 'date-fns'; + import { ko } from 'date-fns/locale'; ... - {created_at.split('T')[0]} + {format(new Date(created_at), 'yyyy년 MM월 dd일', { locale: ko })}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/auth-required/notice/Modal.tsx(1 hunks)src/components/auth-required/notice/Modal.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/auth-required/notice/Modal.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/auth-required/notice/Modal.tsx
[error] 32-32: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (1)
src/components/auth-required/notice/Modal.tsx (1)
9-36: 전체 컴포넌트 구조가 잘 설계되어 있습니다.Modal 컴포넌트가 깔끔하게 구성되어 있으며, 데이터 패칭과 표시를 적절히 처리하고 있습니다. React Query를 활용한 데이터 요청 패턴도 좋은 접근법입니다.
🧰 Tools
🪛 Biome (1.9.4)
[error] 32-32: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
Jihyun3478
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋았던 점
- 크게 이견은 없습니다!
PS. 회의 때 보여주셨던 공지 디자인 배포하면 드디어 볼 수 있는건가요?!👀🤭
Nuung
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋았던 점
- 모달 독립 컴포넌트, 핵심 기능에 맞춰서 잘 기능 개발, 구현된 것 같아서 아주 좋았습니다.
src/components/auth-required/notice/Modal.tsx의dangerouslySetInnerHTML={{ __html: content }}부분과 같이 요구 사항과 align 잘 맞춰서 구현해주신 디테일 좋아요~~
아쉬운 점
- 코멘트 체크 부탁드려요!, 레거시에서 빠진 부분들도 (이벤트 트래킹) 들어가 있는 것 같아서 해당 부분 아쉽네요!
- 배포 전 꼭 게시글을 써야할 것 같은데, 이런 부분도 코멘트나, task 등의 수단으로 기록이 남겨져 있으면 좋을 것 같습니다!
작업 브랜치들을 전부 메인에서 생성해서 그렇습니다.. 사실 좀 애매하긴 한데, 당장은 리팩토링 브랜치 머지 후에 공지사항 브랜치 머지하면서 충돌 해결할 계획입니다~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
src/components/auth-required/notice/index.tsx (1)
55-56:⚠️ Potential issue구문 오류 수정 필요
파일 끝에 불필요한 중괄호와 세미콜론이 있습니다. 이로 인해 구문 오류가 발생할 수 있습니다.
}; -}; -
🧹 Nitpick comments (3)
src/components/auth-required/notice/index.tsx (3)
20-20: 타입 안전성 개선 필요
as string타입 단언을 사용하는 것보다 옵셔널 체이닝과 조건부 렌더링으로 타입 안전성을 개선할 수 있습니다.- data?.posts[0].created_at?.split('T')[0] as string, + (data?.posts[0]?.created_at?.split('T')[0] || new Date().toISOString().split('T')[0]),
31-36: 접근성 개선 필요알림 배너에 접근성(a11y)을 위한 속성이 부족합니다. 스크린 리더 및 키보드 사용자를 위한 적절한 ARIA 속성과 역할을 추가하는 것이 좋습니다.
<div - className={`transition-all shrink-0 duration-300 flex items-center justify-center gap-2 w-full overflow-hidden bg-BORDER-SUB ${show ? 'h-[50px]' : 'h-[0px]'}`} + className={`transition-all shrink-0 duration-300 flex items-center justify-center gap-2 w-full overflow-hidden bg-BORDER-SUB ${show ? 'h-[50px]' : 'h-[0px]'}`} + role="alert" + aria-live="polite" > - <h1 className="text-TEXT-MAIN text-ST4 max-MBI:text-ST5"> + <h2 className="text-TEXT-MAIN text-ST4 max-MBI:text-ST5"> 📣 새로운 업데이트를 확인해보세요! - </h1> + </h2>
37-50: 버튼 접근성 및 로딩 상태 개선 필요버튼에 접근성 속성(예:
aria-label)을 추가하고, 모달 열기 작업이 진행 중일 때 버튼 상태를 표시하는 것이 좋습니다.<button className="text-PRIMARY-MAIN hover:text-PRIMARY-SUB text-ST4 transition-all duration-300 max-MBI:text-ST5" + aria-label="새 업데이트 확인하기" onClick={() => { setShow(false); localStorage.setItem( 'noti_expiry', JSON.stringify(new Date().getTime() + TTL), ); open(<Modal />); }} > 확인하기 </button>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/auth-required/notice/index.tsx(1 hunks)src/components/auth-required/notice/index.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/components/auth-required/notice/index.tsx (1)
Learnt from: Nuung
PR: Check-Data-Out/velog-dashboard-v2-fe#23
File: src/components/auth-required/notice/index.tsx:18-27
Timestamp: 2025-03-11T12:38:34.012Z
Learning: React 컴포넌트에서 API 응답 데이터를 처리할 때는 데이터가 없거나 배열이 비어있는 경우(전체 기능 비활성화, DB 마이그레이션, DBMS 연결 문제 등)에 대비한 방어적 코딩이 필요하다. 특히 `data?.posts[0]`과 같이 배열의 요소에 접근하기 전에 `if (!data?.posts || data.posts.length === 0) return;`와 같은 가드 클로즈를 사용하여 런타임 오류를 방지해야 한다.
🔇 Additional comments (1)
src/components/auth-required/notice/index.tsx (1)
18-27: 데이터 안전성 개선 필요현재 구현은
data?.posts[0]에 직접 접근하여 신규 공지를 확인합니다. 데이터가 없거나 posts 배열이 비어있을 경우 오류가 발생할 수 있습니다. 이전 리뷰에서도 지적된 부분입니다.useEffect(() => { + if (!data?.posts || data.posts.length === 0) return; const lastUpdated = new Date( data?.posts[0].created_at?.split('T')[0] as string, ).getTime(); const lastDate = Math.ceil((new Date().getTime() - lastUpdated) / DAY); if (lastDate <= 4) { const expiry = localStorage.getItem('noti_expiry'); if (!expiry || JSON.parse(expiry) < new Date().getTime()) setShow(true); } }, [data]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
src/components/auth-required/notice/Modal.tsx (1)
36-46:⚠️ Potential issue코드 중복: 36-46 라인에 중복된 코드가 있습니다
파일 끝부분에 중복된 코드가 있습니다. 이 부분은 제거되어야 합니다.
- </div> - <div - className="text-TEXT-MAIN text-I4" - dangerouslySetInnerHTML={{ __html: content }} - ></div> - </div> - ))} - </div> - ); -}; -🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
♻️ Duplicate comments (1)
src/components/auth-required/notice/Modal.tsx (1)
28-31:⚠️ Potential issue보안 취약점: HTML 콘텐츠 삽입 시 XSS 공격 위험이 있습니다.
dangerouslySetInnerHTML을 사용할 때 HTML 콘텐츠를 삽입하기 전에 반드시 적절한 살균(sanitize) 처리가 필요합니다. 현재 구현은 XSS(Cross-Site Scripting) 공격에 취약합니다.다음과 같이 DOMPurify 같은 라이브러리를 사용하여 HTML을 살균하세요:
+ import DOMPurify from 'dompurify'; <div className="text-TEXT-MAIN text-ST5" - dangerouslySetInnerHTML={{ __html: content }} + dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} ></div>필요한 패키지를 설치하려면:
npm install dompurify npm install --save-dev @types/dompurify
🧹 Nitpick comments (3)
src/components/auth-required/notice/Modal.tsx (3)
1-12: 컴포넌트 구조 및 데이터 요청이 잘 설정되어 있습니다.React Query를 사용한 데이터 요청 방식이 깔끔하게 구현되어 있습니다. 다만, 데이터 로딩 상태나 에러 처리 로직이 구현되어 있지 않아 사용자 경험에 영향을 줄 수 있습니다.
다음과 같이 로딩 상태와 에러 처리를 추가하는 것이 좋겠습니다:
const { close } = useModal(); - const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, error } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList });
16-16: 오타가 있습니다: "items-cenetr"className에 오타가 있습니다.
- <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3">공지사항</h2> + <h2 className="text-TEXT-MAIN items-center gap-3 text-T3">공지사항</h2>
20-33: 데이터가 없는 경우의 처리가 필요합니다API에서 데이터를 가져오지 못하거나 posts 배열이 비어있는 경우에 대한 처리가 되어 있지 않습니다.
다음과 같이 데이터가 없는 경우에 대한 처리를 추가하는 것이 좋겠습니다:
- {data?.posts?.map(({ content, created_at, id, title }) => ( + {data?.posts?.length > 0 ? ( + data.posts.map(({ content, created_at, id, title }) => ( <div key={id}> <div className="flex items-center gap-3"> <h3 className="text-TEXT-MAIN text-T4">{title}</h3> <h4 className="text-TEXT-ALT text-T5"> {created_at.split('T')[0]} </h4> </div> <div className="text-TEXT-MAIN text-ST5" dangerouslySetInnerHTML={{ __html: content }} ></div> </div> - ))} + )) + ) : ( + <div className="text-TEXT-ALT text-center py-10">공지사항이 없습니다.</div> + )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/auth-required/notice/Modal.tsx(1 hunks)src/components/auth-required/notice/Modal.tsx(1 hunks)src/components/auth-required/notice/Modal.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/auth-required/notice/Modal.tsx
🔇 Additional comments (2)
src/components/auth-required/notice/Modal.tsx (2)
13-19: 모달 헤더 구현이 잘 되어 있습니다.모달 닫기 기능과 타이틀이 명확하게, 사용성 있게 구현되어 있습니다.
20-27: 날짜 형식 처리에 유틸리티 함수 사용 검토
created_at.split('T')[0]와 같은 날짜 처리 로직은 유틸리티 함수로 추출하는 것이 코드 재사용성과 가독성을 높입니다. 이전 리뷰 코멘트에서도 이 사항이 언급되었습니다.날짜 포맷팅을 위한 유틸리티 함수를 만들어 사용하는 것이 좋겠습니다:
// src/utils/date.ts 파일 생성 + export const formatDate = (isoString: string): string => { + return isoString.split('T')[0]; + }; // Modal.tsx에서 사용 + import { formatDate } from '@/utils/date'; // 사용 부분 - {created_at.split('T')[0]} + {formatDate(created_at)}
Nuung
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이스케이프 처리 (이벤트 핸들링) / 그리고 trackUserEvent 빼야하는거 아녜유?
cheonHwi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋았던 점
- 모달 로직이 독립적으로 설계되어서 유지보수에 어려움이 없을거 같네요!
저도 ESC를 통한 모달 닫기? 가 있으면 좋겠다고 생각했는데, 이미 대응하셨네요. 별 다른 이견 없습니다 고생하셨어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (5)
src/components/Providers/ModalProvider.tsx (1)
6-26: 모달 구현 로직 검토모달 컴포넌트가 잘 구현되었습니다. 모달 외부 클릭 시 닫히는 기능과 배경 어둡게 처리 등 기본적인 모달 기능이 모두 포함되어 있습니다.
몇 가지 개선점을 제안합니다:
- 접근성(a11y) 개선을 위해 Escape 키 처리와 포커스 트랩 추가를 고려해보세요:
'use client'; - import { useRef } from 'react'; + import { useRef, useEffect } from 'react'; import { useModal } from '@/hooks/useModal'; export const ModalProvider = () => { const { Modal, close } = useModal(); const ref = useRef<HTMLDivElement | null>(null); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') close(); + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [close]); if (typeof window !== 'undefined' && Modal) { return ( <div className="w-full h-screen bg-[#000000AA] absolute z-[100] flex items-center justify-center" onMouseDown={({ target }) => { if (ref.current && !ref.current.contains(target as Node)) close(); }} + role="dialog" + aria-modal="true" > <div className="w-fit h-fit" ref={ref}> {Modal} </div> </div> ); } else { return <></>; } };src/app/(auth-required)/components/header/index.tsx (1)
129-134: 공지사항 버튼 구현이 잘 되었습니다.공지사항 버튼이 기존 UI 스타일과 일관성 있게 추가되었고, 모달 열기 기능이 적절하게 구현되었습니다. 다만, 다음과 같은 개선사항을 고려해보세요:
- 접근성 향상을 위해 버튼에
aria-label속성 추가- 호버 효과가 로그아웃 버튼과 다른데, 일관성을 위해 동일한 스타일을 적용하는 것이 좋을 수 있습니다
<button - className="text-TEXT-MAIN text-I3 p-5 max-MBI:p-4 flex whitespace-nowrap w-auto hover:bg-BG-ALT" + className="text-TEXT-MAIN text-I3 p-5 max-MBI:p-4 flex whitespace-nowrap w-auto" + aria-label="공지사항 열기" onClick={() => ModalOpen(<Modal />)} > 공지사항 </button>src/app/(auth-required)/components/notice/Modal.tsx (1)
10-13: 모달 컴포넌트 설계가 적절합니다.
useModal훅의close함수와useQuery를 사용하여 데이터를 가져오는 로직이 잘 구현되었습니다. 다만, 다음 개선사항을 고려해보세요:
- API 호출 실패 시 에러 처리 로직 추가
- 로딩 상태 처리 추가
export const Modal = () => { const { close } = useModal(); - const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, isError } = useQuery({ + queryKey: [PATHS.NOTIS], + queryFn: notiList + });src/app/(auth-required)/components/notice/index.tsx (2)
13-17: Notice 컴포넌트의 구조가 적절합니다.
useQuery를 사용하여 데이터를 가져오고, 상태 관리를 위한useState와 모달 열기 기능을 위한useModal이 적절하게 사용되었습니다. 다만, 다음 개선사항을 고려해보세요:
- API 호출 실패 시 에러 처리 로직 추가
- 로딩 상태 처리 추가
export const Notice = () => { - const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isError } = useQuery({ + queryKey: [PATHS.NOTIS], + queryFn: notiList, + onError: (error) => { + console.error('공지사항 데이터 가져오기 실패:', error); + } + }); const [show, setShow] = useState(false); const { open } = useModal();
30-54: 공지사항 배너 UI 및 로직이 잘 구현되었습니다.공지사항 배너의 UI와 동작 로직이 잘 구현되었습니다. 다만, 다음과 같은 개선을 고려해보세요:
- 웹 접근성 향상을 위한 ARIA 속성 추가
- 로컬 스토리지 오류 처리 추가
- 확인하기 버튼에 키보드 접근성 개선
return ( <> <div + role="alert" + aria-live="polite" className={`transition-all shrink-0 duration-300 flex items-center justify-center gap-2 w-full overflow-hidden bg-BORDER-SUB ${show ? 'h-[50px]' : 'h-[0px]'}`} > <h1 className="text-TEXT-MAIN text-ST4 max-MBI:text-ST5"> 📣 새로운 업데이트를 확인해보세요! </h1> <button className="text-PRIMARY-MAIN hover:text-PRIMARY-SUB text-ST4 transition-all duration-300 max-MBI:text-ST5" + aria-label="공지사항 확인하기" onClick={() => { setShow(false); + try { localStorage.setItem( 'noti_expiry', JSON.stringify(new Date().getTime() + TTL), ); + } catch (error) { + console.error('로컬 스토리지 저장 중 오류 발생:', error); + } open(<Modal />); }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setShow(false); + try { + localStorage.setItem( + 'noti_expiry', + JSON.stringify(new Date().getTime() + TTL), + ); + } catch (error) { + console.error('로컬 스토리지 저장 중 오류 발생:', error); + } + open(<Modal />); + } + }} > 확인하기 </button> </div> </> );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
src/components/Icon/icons/Close.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
src/app/(auth-required)/components/header/index.tsx(3 hunks)src/app/(auth-required)/components/notice/Modal.tsx(1 hunks)src/app/(auth-required)/components/notice/index.tsx(1 hunks)src/app/(auth-required)/layout.tsx(2 hunks)src/app/layout.tsx(2 hunks)src/components/Icon/icons/index.ts(1 hunks)src/components/Providers/ModalProvider.tsx(1 hunks)src/components/Providers/index.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/layout.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/(auth-required)/components/notice/Modal.tsx
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (13)
src/components/Icon/icons/index.ts (1)
7-7: Close 아이콘 추가로 UI 확장성 향상Close 아이콘을 추가함으로써 모달 기능 구현에 필요한 UI 요소가 적절히 추가되었습니다.
src/components/Providers/index.ts (1)
3-3: ModalProvider 내보내기 추가ModalProvider를 index 파일에서 내보내도록 하여 다른 모듈에서 쉽게 접근할 수 있게 되었습니다. 기존의 Provider들과 일관성 있게 구현되었습니다.
src/app/(auth-required)/layout.tsx (3)
5-6: 공지사항 관련 모듈 추가공지사항 기능 구현을 위한 적절한 모듈 import가 추가되었습니다.
18-21: 공지사항 데이터 prefetch 구현공지사항 데이터를 prefetch하여 초기 로딩 시간을 최적화한 좋은 구현입니다.
24-32:Details
❓ Verification inconclusive
레이아웃 구조 변경 및 Notice 컴포넌트 통합
레이아웃 구조가 개선되었고 Notice 컴포넌트가 적절히 통합되었습니다. HydrationBoundary 내에 컴포넌트들을 배치하여 서버 데이터를 클라이언트에서 효과적으로 활용할 수 있도록 했습니다.
다만, 이벤트 추적에 대한 PR 코멘트에서 언급된 내용이 확인되지 않습니다. 배포 전에 이벤트 추적 관련 코드가 필요한지 검토해 보세요.
🌐 Web query:
React에서 이벤트 추적을 위한 best practice💡 Result:
React에서 이벤트 추적을 위한 베스트 프랙티스는 관심사 분리와 선언적 프로그래밍 패턴을 중심으로 구성됩니다. 주요 접근 방식은 다음과 같습니다:
1. 관심사 분리 원칙
- 비즈니스 로직과 추적 로직 분리: 이벤트 핸들러 내에서 직접 분석 함수 호출 대신, 상위 컴포넌트에서 이벤트 가로채기 패턴 사용[4][14]
// 부모 컴포넌트에서 자식의 이벤트 핸들러 확장 const TrackingWrapper = ({ children, onTrack }) => { const enhancedChildren = React.Children.map(children, child => { return React.cloneElement(child, { onClick: (e) => { child.props.onClick?.(e); onTrack(e); } }); }); return enhancedChildren; };2. 선언적 이벤트 추적
- 컴포넌트 프로퍼티를 통한 명시적 추적:
onTrack같은 프로퍼티를 통해 추적 로직 전달[4][12]<Button onClick={handlePayment} onTrack={() => trackEvent('payment_initiated')} />3. 이벤트 전파 활용
- 이벤트 버블링 메커니즘 활용: 중앙 집중식 이벤트 핸들링[1][5]
// 상위 컨테이너에서 포괄적 이벤트 처리 <div onClickCapture={(e) => { if (e.target.dataset.tracking) { analytics.track(e.target.dataset.tracking); } }}> <Button data-tracking="cta_click">Click</Button> </div>4. 커스텀 훅 활용
- 추적 로직 추상화:
useTracking훅을 만들어 재사용성 확보[15][14]const useTracking = (eventName) => { const track = useCallback((payload) => { analytics.track(eventName, payload); }, [eventName]); return track; }; // 사용 예시 const trackButtonClick = useTracking('button_click');5. 이벤트 객체 확장
- SyntheticEvent 활용: React의 이벤트 래퍼 사용[12][3]
const handleClick = (e) => { e.persist(); // 비동기 이벤트 처리 시 필요 trackEvent('click', { element: e.currentTarget.tagName, coordinates: {x: e.clientX, y: e.clientY} }); };6. 이벤트 전파 제어
- stopPropagation vs stopImmediatePropagation: 이벤트 전파 단계 제어[1][5]
const handleClick = (e) => { e.stopPropagation(); // 리액트 트리 전파만 중지 e.nativeEvent.stopImmediatePropagation(); // 기본 DOM 전파 중지 };
패턴 장점 사용 사례 HOC 래퍼 기존 컴포넌트 수정 불필요 공통 추적 로직 적용 커스텀 훅 로직 재사용성 향상 복잡한 추적 시나리오 이벤트 버블링 중앙 집중식 관리 페이지 전체 이벤트 추적 데이터 속성 선언적 설정 가능 동적 이벤트 추적 이러한 패턴들은 코드의 가독성을 유지하면서도 확장 가능한 이벤트 추적 시스템을 구축할 수 있게 해줍니다. 특히 이벤트 전파 메커니즘과 React의 컴포넌트 구성 방식을 결합하면, 애플리케이션 전반에 걸친 통합 추적 시스템을 구현할 수 있습니다[5][12].
Citations:
- 1: https://ko.react.dev/learn/responding-to-events
- 2: https://velog.io/@tnghgks/React-React-%EC%97%90%EC%84%9C%EB%8A%94-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B2%98%EB%A6%AC%ED%95%A0%EA%B9%8C
- 3: https://www.howdy-mj.me/react/event-handling-in-react
- 4: https://lasbe.tistory.com/182
- 5: https://blog.mathpresso.com/react-deep-dive-react-event-system-1-759523d90341
- 6: https://velog.io/@tlsl13/React-EventHandling
- 7: https://dev.to/sidramaqbool/handling-events-in-react-with-best-practices-and-examples-1d75
- 8: https://jeongola.tistory.com/entry/React-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%B2%98%EB%A6%AC
- 9: https://velog.io/@hang_kem_0531/React-Event-Handling
- 10: https://doyu-l.tistory.com/273
- 11: https://tech.kakaopay.com/post/react-query-1/
- 12: https://velog.io/@yoonvelog/React-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%B2%98%EB%A6%AC
- 13: https://react.dev/learn/referencing-values-with-refs
- 14: https://lakelouise.tistory.com/261
- 15: https://velog.io/@yena1025/%EB%A6%AC%EC%95%A1%ED%8A%B8-Best-Practice-Pattern-1%EB%B6%80
- 16: https://ko.legacy.reactjs.org/docs/handling-events.html
- 17: https://ktseo41.github.io/blog/log/avoid-these-common-pitfalls-of-react-usestate.html
- 18: https://www.nextree.io/react-handling-event-5/
- 19: https://www.freecodecamp.org/korean/news/best-practices-for-react/
- 20: https://blog.naver.com/rbals0445/223132439845?viewType=pc
레이아웃 통합은 잘 반영되었으나, 이벤트 추적 구현 여부를 재검토해 주세요
- 레이아웃 구조 변경과 Notice 컴포넌트 통합, HydrationBoundary 적용은 적절히 개선되었습니다.
- 다만, PR 코멘트에서 언급한 이벤트 추적 관련 구현은 확인되지 않았습니다.
- React의 이벤트 추적 베스트 프랙티스(관심사 분리, 선언적 이벤트 추적, HOC 또는 커스텀 훅 활용 등)를 참고하여, 배포 전에 이벤트 추적에 필요한 코드 추가 여부를 검토해 주시기 바랍니다.
src/app/(auth-required)/components/header/index.tsx (3)
12-14: 새로운 의존성 추가가 적절합니다.모달 기능에 필요한
useModal훅과Modal컴포넌트를 가져오는 import 구문이 깔끔하게 추가되었습니다.
33-33: useModal 훅의 open 함수를 적절하게 활용했습니다.
open함수를ModalOpen으로 이름을 변경하여 사용함으로써 코드의 가독성을 높였습니다.
122-122: 스타일 변경이 적절하게 이루어졌습니다.드롭다운 메뉴의 스타일 변경이 일관되게 적용되었습니다.
src/app/(auth-required)/components/notice/Modal.tsx (3)
3-9: 의존성 import가 잘 구성되었습니다.필요한 의존성들이 깔끔하게 import 되었습니다.
14-19: Escape 키 이벤트 처리가 적절합니다.모달을 닫기 위한 키보드 이벤트 처리가 잘 구현되었습니다. 클린업 함수도 올바르게 추가되었습니다.
21-28: 모달 헤더 스타일 및 구조가 잘 구현되었습니다.모달 헤더의 레이아웃과 스타일이 잘 구현되었습니다. 클로즈 버튼도 추가되어 사용자 경험이 좋습니다.
src/app/(auth-required)/components/notice/index.tsx (2)
3-9: import 문이 잘 구성되어 있습니다.필요한 의존성들이 깔끔하게 import 되었습니다.
10-12: 상수 정의가 명확합니다.
DAY와TTL상수를 활용해 코드의 가독성을 높였습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (3)
src/app/(auth-required)/components/notice/Modal.tsx (3)
12-12: 🛠️ Refactor suggestion로딩 및 에러 상태 처리가 누락되었습니다.
API 호출 시 로딩 및 에러 상태에 대한 처리가 없습니다. 이로 인해 데이터 로딩 중이거나 오류 발생 시 사용자 경험이 저하될 수 있습니다.
useQuery에서 isLoading과 isError 상태를 추출하여 적절한 UI를 제공하세요:
- const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, isError } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList });그리고 해당 상태에 따라 UI를 조건부로 렌더링하세요:
{isLoading && ( <div className="flex items-center justify-center h-full"> <p className="text-TEXT-ALT">로딩 중...</p> </div> )} {isError && ( <div className="flex items-center justify-center h-full"> <p className="text-DESTRUCTIVE-MAIN">공지사항을 불러오는 중 오류가 발생했습니다.</p> </div> )} {!isLoading && !isError && data?.posts?.length === 0 && ( <div className="flex items-center justify-center h-full"> <p className="text-TEXT-ALT">표시할 공지사항이 없습니다.</p> </div> )}
21-28: 🛠️ Refactor suggestion모달의 접근성을 개선해야 합니다.
현재 모달은 기본적인 접근성 속성이 누락되어 있습니다. 스크린 리더 사용자가 모달을 인식하고 제대로 상호작용할 수 있도록 ARIA 속성을 추가해야 합니다.
다음과 같이 수정하세요:
return ( - <div className="w-[800px] h-[500px] max-MBI:w-[450px] max-MBI:h-[200px] overflow-auto flex flex-col gap-3 p-10 max-MBI:p-7 rounded-md bg-BG-SUB"> + <div + role="dialog" + aria-labelledby="modal-title" + className="w-[800px] h-[500px] max-MBI:w-[450px] max-MBI:h-[200px] overflow-auto flex flex-col gap-3 p-10 max-MBI:p-7 rounded-md bg-BG-SUB"> <div className="flex items-center justify-between"> - <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3 max-MBI:text-T4"> + <h2 id="modal-title" className="text-TEXT-MAIN items-center gap-3 text-T3 max-MBI:text-T4"> 공지사항 </h2> - <Icon name="Close" onClick={close} className="cursor-pointer" /> + <Icon name="Close" onClick={close} className="cursor-pointer" aria-label="모달 닫기" /> </div>그리고 오타를 수정했습니다 (
items-cenetr→items-center).
30-43:⚠️ Potential issueXSS 취약점 및 데이터 존재 여부 확인이 필요합니다.
dangerouslySetInnerHTML은 XSS 공격에 취약하므로 보안 이슈가 발생할 수 있습니다.data?.posts확인 후 바로 map을 사용하면 posts가 빈 배열일 경우 UI 처리가 누락됩니다.아래와 같이 변경하세요:
- {data?.posts?.map(({ content, created_at, id, title }) => ( + {!isLoading && !isError && data?.posts?.length > 0 && data.posts.map(({ content, created_at, id, title }) => ( <div key={id} className="flex flex-col gap-2"> <div className="flex items-center gap-3"> <h3 className="text-TEXT-MAIN text-T4 max-MBI:text-T5">{title}</h3> <h4 className="text-TEXT-ALT text-T5 max-MBI:text-ST5"> {created_at.split('T')[0]} </h4> </div> <div className="text-TEXT-MAIN text-I4 prose" - dangerouslySetInnerHTML={{ __html: content }} + dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} /> </div> ))}DOMPurify를 사용하기 위해서는 패키지를 설치하고 임포트해야 합니다:
npm install dompurify npm install @types/dompurify --save-devimport DOMPurify from 'dompurify';또는 html-react-parser를 사용하는 방법도 있습니다:
npm install html-react-parser+ import parse from 'html-react-parser'; // ... - <div - className="text-TEXT-MAIN text-I4 prose" - dangerouslySetInnerHTML={{ __html: content }} - /> + <div className="text-TEXT-MAIN text-I4 prose"> + {parse(content)} + </div>보안을 위해 API에서 받아오는 HTML 콘텐츠가 신뢰할 수 있는 소스인지 확인하세요.
🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🧹 Nitpick comments (1)
src/app/(auth-required)/components/notice/Modal.tsx (1)
24-26: 텍스트의 오타를 수정해야 합니다.'items-cenetr'은 오타입니다. 'items-center'로 수정해야 합니다.
다음과 같이 오타를 수정하세요:
- <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3 max-MBI:text-T4"> + <h2 className="text-TEXT-MAIN items-center gap-3 text-T3 max-MBI:text-T4">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (3)
package.json(2 hunks)src/app/(auth-required)/components/notice/Modal.tsx(1 hunks)tailwind.config.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/(auth-required)/components/notice/Modal.tsx
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (2)
tailwind.config.ts (1)
2-2: @tailwindcss/typography 플러그인 추가 승인Typography 플러그인을 추가한 것은 좋은 선택입니다. 이 플러그인은 HTML 콘텐츠를 렌더링할 때 (특히
dangerouslySetInnerHTML을 사용할 때) 깔끔한 타이포그래피 스타일을 제공하므로 공지사항 텍스트 표시에 적합합니다.Also applies to: 25-25
src/app/(auth-required)/components/notice/Modal.tsx (1)
22-22: 모달 백그라운드 오버레이가 누락되었습니다.현재 모달은 컨테이너만 있고 배경 오버레이가 누락되어 있습니다. 이로 인해 사용자 경험이 저하되고 모달의 시각적 위계가 명확하지 않을 수 있습니다.
모달 컴포넌트를 수정하여 배경 오버레이를 추가하세요:
return ( + <div + className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" + onClick={(e) => { + if (e.target === e.currentTarget) close(); + }} + > <div className="w-[800px] h-[500px] max-MBI:w-[450px] max-MBI:h-[200px] overflow-auto flex flex-col gap-3 p-10 max-MBI:p-7 rounded-md bg-BG-SUB"> {/* 기존 모달 내용 */} </div> + </div> );이렇게 하면 모달 외부를 클릭했을 때 모달이 닫히는 기능도 추가됩니다.
| fontSize: FONTS, | ||
| }, | ||
| plugins: [], | ||
| plugins: [typograpy()], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
플러그인 사용에 오타가 있습니다.
import에서와 같은 오타가 여기에도 있습니다. 올바른 이름은 typography입니다.
다음과 같이 수정해주세요:
- plugins: [typograpy()],
+ plugins: [typography()],📝 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.
| plugins: [typograpy()], | |
| plugins: [typography()], |
| @@ -1,4 +1,5 @@ | |||
| import type { Config } from 'tailwindcss'; | |||
| import typograpy from '@tailwindcss/typography'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import 이름에 오타가 있습니다.
typograpy에 오타가 있습니다. 올바른 이름은 typography입니다.
다음과 같이 수정해주세요:
-import typograpy from '@tailwindcss/typography';
+import typography from '@tailwindcss/typography';📝 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.
| import typograpy from '@tailwindcss/typography'; | |
| import typography from '@tailwindcss/typography'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/app/(auth-required)/components/notice/Modal.tsx (1)
39-40:⚠️ Potential issueXSS 보안 취약점이 있습니다.
dangerouslySetInnerHTML을 사용하면 XSS(Cross-Site Scripting) 공격에 취약해질 수 있습니다. API에서 받은 HTML 콘텐츠가 신뢰할 수 있는 소스에서 왔더라도, 안전한 렌더링을 위해 HTML 샌드박스 라이브러리를 사용하는 것이 좋습니다.다음과 같이 개선해 보세요:
- <div - className="text-TEXT-MAIN text-I4 prose" - dangerouslySetInnerHTML={{ __html: content }} - /> + import DOMPurify from 'dompurify'; + // 또는 + import parse from 'html-react-parser'; + + // DOMPurify 사용 시 + <div className="text-TEXT-MAIN text-I4 prose" + dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }} + /> + + // html-react-parser 사용 시 + <div className="text-TEXT-MAIN text-I4 prose"> + {parse(content)} + </div>🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🧹 Nitpick comments (3)
src/app/(auth-required)/components/notice/Modal.tsx (3)
35-35: 날짜 형식 처리 방식을 개선하세요.
created_at.split('T')[0]는 간단하지만 제한적인 방식입니다. 더 사용자 친화적인 날짜 형식을 위해Intl.DateTimeFormat이나 날짜 라이브러리 사용을 고려하세요.- {created_at.split('T')[0]} + {new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: 'long', + day: 'numeric' + }).format(new Date(created_at))}
12-12: API 요청 실패에 대한 오류 처리가 필요합니다.현재 코드는 API 요청이 실패할 경우에 대한 처리가 없습니다. useQuery에서 제공하는 isLoading, isError 상태와 error 객체를 활용하여 더 완전한 사용자 경험을 제공하세요.
- const { data } = useQuery({ queryKey: [PATHS.NOTIS], queryFn: notiList }); + const { data, isLoading, isError, error } = useQuery({ + queryKey: [PATHS.NOTIS], + queryFn: notiList, + retry: 1, + onError: (err) => { + console.error('공지사항 데이터 로딩 실패:', err); + // 선택적으로 에러 로깅 또는 알림 기능 추가 + } + });
24-24: 오타가 발견되었습니다.
items-cenetr에 오타가 있습니다. 올바른 클래스명은items-center입니다.- <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3 max-MBI:text-T4"> + <h2 className="text-TEXT-MAIN items-center gap-3 text-T3 max-MBI:text-T4">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/app/(auth-required)/components/notice/Modal.tsx(1 hunks)src/app/(auth-required)/components/notice/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/(auth-required)/components/notice/index.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
src/app/(auth-required)/components/notice/Modal.tsx
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🔇 Additional comments (1)
src/app/(auth-required)/components/notice/Modal.tsx (1)
21-45: 모달 UI 개선 사항을 제안합니다.현재 모달 구현에 다음과 같은 개선이 필요합니다:
- 로딩, 에러, 데이터 없음 상태에 대한 UI 처리가 없습니다.
- 웹 접근성을 위한 ARIA 속성이 누락되었습니다.
- 모달 배경 오버레이가 없어 UX가 저하됩니다.
return ( - <div className="w-[800px] h-[500px] max-MBI:w-[450px] max-MBI:h-[200px] overflow-auto flex flex-col gap-3 p-10 max-MBI:p-7 rounded-md bg-BG-SUB"> + <div + role="dialog" + aria-labelledby="modal-title" + className="w-[800px] h-[500px] max-MBI:w-[450px] max-MBI:h-[200px] overflow-auto flex flex-col gap-3 p-10 max-MBI:p-7 rounded-md bg-BG-SUB" + > <div className="flex items-center justify-between"> - <h2 className="text-TEXT-MAIN items-cenetr gap-3 text-T3 max-MBI:text-T4"> + <h2 id="modal-title" className="text-TEXT-MAIN items-center gap-3 text-T3 max-MBI:text-T4"> 공지사항 </h2> - <Icon name="Close" onClick={close} className="cursor-pointer" /> + <Icon name="Close" onClick={close} className="cursor-pointer" aria-label="모달 닫기" /> </div> + {data === undefined && ( + <div className="flex items-center justify-center h-full"> + <p className="text-TEXT-ALT">로딩 중...</p> + </div> + )} + {data === null && ( + <div className="flex items-center justify-center h-full"> + <p className="text-DESTRUCTIVE-MAIN">공지사항을 불러오는 중 오류가 발생했습니다.</p> + </div> + )} + {data?.posts?.length === 0 && ( + <div className="flex items-center justify-center h-full"> + <p className="text-TEXT-ALT">표시할 공지사항이 없습니다.</p> + </div> + )} - {data?.posts?.map(({ content, created_at, id, title }) => ( + {data?.posts && data.posts.length > 0 && data.posts.map(({ content, created_at, id, title }) => (🧰 Tools
🪛 Biome (1.9.4)
[error] 40-40: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
공지사항 기능을 드디어 완성했습니다!
따지고 보면 단순한 기능인데, UI 디자인 선정이랑 일정때문에 오늘에서야 완성되네요~
Summary by CodeRabbit