[FEAT] AI 독후감 구현#286
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAI 작문 생성 기능을 추가합니다. 사용자가 메모리 추가 버튼에서 "AI 독서 감상문 생성"을 선택하면 확인 모달이 나타나고, 확인 후 AIWrite 페이지로 이동하여 로딩 후 생성된 내용을 표시하고 클립보드에 복사할 수 있습니다. 또한 ConfirmModal의 버튼 텍스트를 커스터마이징 가능하게 변경했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant MemoryAddButton
participant PopupStore
participant AIWrite as AIWrite Page
participant Clipboard
User->>MemoryAddButton: "AI 독서 감상문 생성" 선택
MemoryAddButton->>PopupStore: openConfirm() 호출
PopupStore->>User: 확인 모달 표시
User->>PopupStore: 확인 클릭
PopupStore->>PopupStore: closePopup()
PopupStore->>AIWrite: /aiwrite/{roomId}로 네비게이션
AIWrite->>AIWrite: 5초 로딩 상태 표시
AIWrite->>AIWrite: 로딩 완료 후 결과 뷰 표시
User->>AIWrite: "클립보드에 복사" 버튼 클릭
AIWrite->>Clipboard: MOCK_AI_WRITING 복사
Clipboard-->>AIWrite: 성공/실패 응답
AIWrite->>User: 스낵바 메시지 표시
User->>AIWrite: 뒤로 가기 클릭
AIWrite->>PopupStore: openConfirm() 호출
PopupStore->>User: 나가기 확인 모달 표시
User->>PopupStore: 확인 클릭
PopupStore->>AIWrite: /rooms/{roomId}/memory로 네비게이션
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 분 주의할 사항:
Possibly related issues
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/components/common/Modal/PopupContainer.tsx (1)
44-44: 백드롭 클릭으로 모달이 닫히는 동작 확인 필요
Wrapper에onClick={closePopup}을 추가하여 모달 외부 클릭 시 닫히도록 구현했습니다. 일반적인 UX 패턴이지만, AIWrite 페이지의 뒤로가기 확인 모달처럼 중요한 경고("생성된 감상문은 다시 볼 수 없으며...")가 있는 경우 사용자가 실수로 백드롭을 클릭해 모달을 닫을 수 있습니다.중요한 확인 모달의 경우 명시적인 버튼 클릭만 허용하는 것을 고려해보세요. 예를 들어,
ConfirmModalProps에disableBackdropClick?: boolean옵션을 추가하거나, 중요한 작업의 경우 별도의 모달 타입을 사용하는 방법이 있습니다.src/pages/aiwrite/AIWrite.tsx (2)
166-179: 모바일 세이프 에리어 고려 필요
CopyButton이 화면 하단에 고정(position: fixed; bottom: 0)되어 있지만, 최신 모바일 기기의 홈 인디케이터나 노치를 고려하지 않았습니다. 일부 기기에서 버튼이 시스템 UI에 가려질 수 있습니다.CSS
safe-area-inset적용을 권장합니다:const CopyButton = styled.button` position: fixed; bottom: 0; + bottom: env(safe-area-inset-bottom); + padding-bottom: calc(env(safe-area-inset-bottom) / 2); width: 100%; height: 50px;또는 height를 동적으로 조정:
const CopyButton = styled.button` position: fixed; bottom: 0; width: 100%; - height: 50px; + height: calc(50px + env(safe-area-inset-bottom)); + padding-bottom: env(safe-area-inset-bottom); background-color: ${colors.purple.main};
129-135: 모바일 키보드 환경 고려 (선택사항)Line 133에서
height: calc(100vh - 56px)를 사용하고 있습니다. 모바일에서 키보드가 표시될 때100vh는 키보드를 포함한 전체 높이를 참조하여, 일부 콘텐츠가 키보드에 가려질 수 있습니다.현재는
overflow-y: auto로 스크롤이 가능하므로 큰 문제는 아니지만, 필요시 CSSdvh(Dynamic Viewport Height) 단위를 고려할 수 있습니다 (최신 브라우저에서 지원).- height: calc(100vh - 56px); + height: calc(100dvh - 56px); /* 또는 min-height 사용 */브라우저 호환성 확인 후 적용하시기 바랍니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
src/assets/common/infoIcon_white.svgis excluded by!**/*.svgsrc/assets/memory/ai.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
src/components/common/Modal/ConfirmModal.tsx(1 hunks)src/components/common/Modal/PopupContainer.tsx(1 hunks)src/components/memory/MemoryAddButton/MemoryAddButton.tsx(3 hunks)src/mocks/aiwrite.mock.ts(1 hunks)src/pages/aiwrite/AIWrite.tsx(1 hunks)src/pages/index.tsx(2 hunks)src/stores/usePopupStore.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/common/Modal/ConfirmModal.tsx (1)
src/stores/usePopupStore.ts (1)
ConfirmModalProps(12-19)
src/components/memory/MemoryAddButton/MemoryAddButton.tsx (2)
src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/components/memory/MemoryAddButton/MemoryAddButton.styled.ts (1)
DropdownItem(67-94)
src/pages/aiwrite/AIWrite.tsx (3)
src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/mocks/aiwrite.mock.ts (1)
MOCK_AI_WRITING(1-19)src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
🪛 ast-grep (0.39.6)
src/components/common/Modal/ConfirmModal.tsx
[warning] 19-19: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
src/components/common/Modal/ConfirmModal.tsx
[error] 20-20: 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 (8)
src/pages/index.tsx (1)
43-43: LGTM!라우팅 설정이 올바르게 구현되었습니다. 기존 패턴과 일관성 있게 AIWrite 컴포넌트를 추가했습니다.
Also applies to: 68-68
src/stores/usePopupStore.ts (1)
17-18: LGTM!
ConfirmModalProps에 선택적 프로퍼티를 추가하여 모달 버튼 텍스트를 커스터마이징할 수 있게 했습니다. 기존 코드와의 하위 호환성이 유지됩니다.src/components/memory/MemoryAddButton/MemoryAddButton.tsx (1)
90-93: LGTM!AI 독서 감상문 생성 드롭다운 항목이 올바르게 추가되었습니다. 아이콘과 핸들러 연결이 적절합니다.
src/mocks/aiwrite.mock.ts (1)
1-19: LGTM!AI 작문 기능의 목업 데이터가 적절하게 구현되었습니다. 개발 및 테스트에 사용하기에 충분한 길이와 내용을 가지고 있습니다.
src/components/common/Modal/ConfirmModal.tsx (1)
5-15: LGTM!커스터마이징 가능한 버튼 텍스트와
stopPropagation처리가 올바르게 구현되었습니다. 기본값('예', '아니요')도 적절하며, 모달 컨텐츠 클릭 시 백드롭 클릭 이벤트 전파를 차단하는 로직이 정확합니다.Also applies to: 23-26
src/pages/aiwrite/AIWrite.tsx (3)
18-24: LGTM!5초 로딩 타이머가 올바르게 구현되었으며, cleanup 함수로 메모리 누수를 방지하고 있습니다.
39-56: LGTM!클립보드 복사 기능이 올바르게 구현되었습니다. 에러 처리와 사용자 피드백(스낵바)이 적절합니다.
navigator.clipboardAPI는 HTTPS 환경에서 작동하므로, 로컬 개발 환경에서는localhost를 사용하고 프로덕션은 HTTPS를 사용하면 됩니다.
58-86: 전반적으로 잘 구현되었습니다!AIWrite 컴포넌트의 전체 구조와 로직이 깔끔하게 구현되었습니다. 로딩 상태 관리, 조건부 렌더링, 그리고 팝업 시스템 통합이 모두 적절합니다. 위에서 언급한 이용횟수 표시와 모바일 세이프 에리어 이슈만 개선하면 프로덕션에 적합할 것으로 보입니다.
| <Container> | ||
| <Container onClick={handleContainerClick}> | ||
| <div className="title">{title}</div> | ||
| <div className="disc" dangerouslySetInnerHTML={{ __html: disc }} /> |
There was a problem hiding this comment.
XSS 취약점 방지를 위한 HTML 새니타이제이션 필요
Line 20에서 dangerouslySetInnerHTML을 사용하여 disc 프로퍼티를 렌더링하고 있습니다. 현재는 정적 문자열(<br/> 태그 포함)만 전달되므로 안전하지만, 향후 사용자 입력이나 외부 데이터가 포함될 경우 XSS(Cross-Site Scripting) 공격에 취약해질 수 있습니다.
DOMPurify 라이브러리를 사용하여 HTML을 새니타이징하는 것을 권장합니다:
+import DOMPurify from 'dompurify';
+
const ConfirmModal = ({
title,
disc,
onConfirm,
onClose,
confirmText = '예',
cancelText = '아니요',
}: ConfirmModalProps) => {
const handleContainerClick = (e: React.MouseEvent) => {
e.stopPropagation();
};
+
+ const sanitizedDisc = DOMPurify.sanitize(disc);
return (
<Container onClick={handleContainerClick}>
<div className="title">{title}</div>
- <div className="disc" dangerouslySetInnerHTML={{ __html: disc }} />
+ <div className="disc" dangerouslySetInnerHTML={{ __html: sanitizedDisc }} />
<ButtonContainer>DOMPurify 설치:
npm install dompurify
npm install --save-dev @types/dompurify🧰 Tools
🪛 Biome (2.1.2)
[error] 20-20: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In src/components/common/Modal/ConfirmModal.tsx around line 20, rendering disc
via dangerouslySetInnerHTML without sanitization risks XSS if disc ever contains
user/external input; import DOMPurify, sanitize the disc value (e.g., const
safeDisc = DOMPurify.sanitize(disc)) and use the sanitized string in
dangerouslySetInnerHTML, ensuring DOMPurify is installed and types added as dev
dependency.
| const handleAIWrite = () => { | ||
| setIsOpen(false); | ||
| openConfirm({ | ||
| title: 'AI 독서감상문 생성 (Beta)', | ||
| disc: '기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : n/5)', | ||
| confirmText: '확인', | ||
| cancelText: '취소', | ||
| onConfirm: () => { | ||
| closePopup(); | ||
| const currentRoomId = roomId || '1'; | ||
| navigate(`/aiwrite/${currentRoomId}`); | ||
| console.log('AI 독서 감상문 생성 시작 - roomId:', currentRoomId); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
하드코딩된 이용횟수를 동적 데이터로 대체 필요
Line 62에서 "(서비스 내 잔여 이용횟수 : n/5)"가 하드코딩되어 있습니다. 실제 사용자의 잔여 이용횟수를 서버나 상태 관리에서 가져와 동적으로 표시해야 합니다.
다음과 같이 수정할 수 있습니다:
+ // 사용자의 실제 이용횟수를 가져오는 hook/API 호출 필요
+ const { remainingCount, totalCount } = useAIWritingQuota(); // 예시
const handleAIWrite = () => {
setIsOpen(false);
openConfirm({
title: 'AI 독서감상문 생성 (Beta)',
- disc: '기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : n/5)',
+ disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${remainingCount}/${totalCount})`,
confirmText: '확인',
cancelText: '취소',
onConfirm: () => {참고: Line 62의 disc는 ConfirmModal에서 dangerouslySetInnerHTML로 렌더링되므로, 사용자 입력이 포함된 데이터를 사용할 경우 XSS 방어를 위해 sanitize 처리가 필요합니다. 현재는 정적 문자열이므로 안전하지만, 동적 데이터 추가 시 주의하세요.
Committable suggestion skipped: line range outside the PR's diff.
| const handleBackClick = () => { | ||
| openConfirm({ | ||
| title: 'AI 독서감상문 생성 (Beta)', | ||
| disc: '생성된 감상문은 다시 볼 수 없으며, 잔여 이용횟수는 차감돼요. 계속하시겠어요?', | ||
| confirmText: '확인', | ||
| cancelText: '취소', | ||
| onConfirm: () => { | ||
| closePopup(); | ||
| navigate(`/rooms/${roomId}/memory`); | ||
| }, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
이용횟수 관련 메시지 일관성 개선 필요
Line 29에서 "잔여 이용횟수는 차감돼요"라는 메시지를 표시하고 있지만, 실제 잔여 횟수를 보여주지 않습니다. MemoryAddButton에서는 "n/5" 형태로 표시하고 있어 일관성이 부족합니다.
사용자에게 현재 잔여 횟수를 명확히 알려주는 것이 더 나은 UX입니다.
const handleBackClick = () => {
+ // 실제 이용횟수를 가져오는 hook/API 필요
+ const { remainingCount, totalCount } = useAIWritingQuota(); // 예시
+
openConfirm({
title: 'AI 독서감상문 생성 (Beta)',
- disc: '생성된 감상문은 다시 볼 수 없으며, 잔여 이용횟수는 차감돼요. 계속하시겠어요?',
+ disc: `생성된 감상문은 다시 볼 수 없으며, 잔여 이용횟수가 차감됩니다 (현재: ${remainingCount}/${totalCount}). 계속하시겠어요?`,
confirmText: '확인',Committable suggestion skipped: line range outside the PR's diff.
#️⃣ 연관된 이슈
📝 작업 내용
1️⃣ 플로팅 버튼 메뉴 추가
2️⃣ AI 독서감상문 생성 확인 모달
3️⃣ AI 독서감상문 로딩 페이지
/aiwrite/:roomId라우트 추가4️⃣ AI 독서감상문 결과 화면
5️⃣ 뒤로가기 확인 모달
6️⃣ 목업 데이터 및 파일 구조
src/mocks/aiwrite.mock.ts생성 - 레이 커즈와일 "특이점이 시작된다" 서평 형식src/pages/aiwrite/AIWrite.tsx생성 - 메인 페이지 컴포넌트주요 변경 파일
src/components/memory/MemoryAddButton/MemoryAddButton.tsx- AI 생성 메뉴 추가src/stores/usePopupStore.ts- ConfirmModal props에 버튼 텍스트 커스터마이징 추가src/components/common/Modal/ConfirmModal.tsx- confirmText, cancelText props 추가src/components/common/Modal/PopupContainer.tsx- 외부 클릭 시 모달 닫기 기능src/pages/index.tsx- 라우팅 추가src/mocks/aiwrite.mock.ts- 목업 데이터src/pages/aiwrite/AIWrite.tsx- AI 독서감상문 페이지스크린샷 (선택)
2025-10-28.9.30.15.mov
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항