-
Notifications
You must be signed in to change notification settings - Fork 0
develp branch 작업내용 머지 : develop -> main #295
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
base: main
Are you sure you want to change the base?
Conversation
commit history
…to chore/minor-updates
[FEAT] 인플루언서 추천 피드 - 지현
…to chore/minor-updates
[FEAT] AI 독후감 구현
[API] AI 독서감상문 생성 및 AI 이용횟수 조회 API 연동
feat: 모임 생성 시 책 선택에서 모임 책 탭 숨김 처리
모임 메인 페이지 사용성 개선 2차 작업
fix: 완료된 모임방 접근 개선
…to chore/minor-updates
Revert "Merge pull request #284 from THIP-TextHip/feat/influencer"
…to chore/minor-updates
fix: 사용자 아이콘 변경 및 투표 생성 가이드 메세지 수정
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAI 독서 감상문 생성 기능 추가 및 그룹 완료 상태 관리 개선. 새로운 AI 리뷰 API 통합, AIWrite 페이지 컴포넌트, 메모리 추가 버튼의 AI 기능, 북 검색의 그룹 탭 조건부 표시, 그룹 카드의 완료 상태 표시, 모달 텍스트 커스터마이제이션 지원. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant MemoryAddButton
participant Popup as usePopupActions
participant API as getAiUsage
participant Store as usePopupStore
participant Page as AIWrite Page
participant Copy as Copy Button
User->>MemoryAddButton: AI 독서 감상문 생성 클릭
MemoryAddButton->>API: getAiUsage(roomId)
API-->>MemoryAddButton: {recordReviewCount, recordCount}
alt 사용량 체크 실패
MemoryAddButton->>Popup: openSnackbar (에러메시지)
else 기록 < 2개
MemoryAddButton->>Popup: openSnackbar (최소 2개 필요)
else 사용 횟수 >= 5
MemoryAddButton->>Popup: openSnackbar (최대 사용 횟수 초과)
else 유효함
MemoryAddButton->>Popup: openConfirm (확인 모달)
Popup->>Store: 모달 열기
User->>Store: 확인 버튼 클릭
Store->>Page: /aiwrite/{roomId} 네비게이션
Page->>API: createAiReview(roomId)
API-->>Page: {content, count} (AI 생성 감상문)
Page->>User: 감상문 표시
User->>Copy: 복사 버튼 클릭
Copy->>User: 스낵바 (복사 완료)
end
sequenceDiagram
participant Component as BookSearchBottomSheet
participant Hook as useBookSearch
participant API as API
Component->>Component: showGroupTab 속성 (기본값: true)
Component->>Hook: useBookSearch(showGroupTab)
alt showGroupTab === true
Hook->>API: 그룹 데이터 로드
else showGroupTab === false
Hook->>Hook: 그룹 데이터 스킵
end
Hook-->>Component: {savedBooks, groupBooks}
Component->>Component: BookSearchTabs(showGroupTab)
alt showGroupTab === true
Component->>Component: 그룹 탭 렌더
else showGroupTab === false
Component->>Component: 그룹 탭 숨김
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 주의 깊게 검토해야 할 영역:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/components/pollwrite/PollCreationSection.tsx (1)
111-111: 플레이스홀더 텍스트 개선이 적절합니다."투표 내용"에서 "투표 제목"으로 변경하여 사용자가 입력해야 할 내용을 더 명확하게 전달합니다.
선택 사항: 향후 리팩토링 시,
content와onContentChangeprop 이름을title과onTitleChange로 변경하는 것을 고려해보세요. 이를 통해 UI 텍스트와 코드 네이밍의 일관성을 높일 수 있습니다.src/components/common/Modal/ConfirmModal.tsx (1)
20-20:dangerouslySetInnerHTML패턴은 현재 안전하지만, 방어적 개선을 고려하세요.검증 결과:
discprop의 모든 사용처를 조사한 결과, 현재 코드는 XSS 취약점이 없습니다. 대부분 개발자가 작성한 하드코딩된 문자열이고, 유일한 동적 값인recordReviewCount는 숫자 타입으로 타입 안전성이 보장됩니다.그러나 주의할 점:
dangerouslySetInnerHTML패턴은 구조적으로 위험합니다. 향후 사용자 입력이나 API 응답을disc에 추가할 때 안전성이 보장되지 않습니다.선택사항:
<br/>태그가 필요한 경우, JSX 요소나 CSS로 대체 검토- 방어적 목적으로 DOMPurify 라이브러리 추가 고려
discprop에 오직 안전한 소스만 전달한다는 규칙을 문서화
📜 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 (3)
src/assets/common/infoIcon_white.svgis excluded by!**/*.svgsrc/assets/feed/people.svgis excluded by!**/*.svgsrc/assets/memory/ai.svgis excluded by!**/*.svg
📒 Files selected for processing (21)
src/api/record/createAiReview.ts(1 hunks)src/api/record/getAiUsage.ts(1 hunks)src/api/record/index.ts(1 hunks)src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx(3 hunks)src/components/common/BookSearchBottomSheet/BookSearchTabs.tsx(1 hunks)src/components/common/BookSearchBottomSheet/useBookSearch.ts(6 hunks)src/components/common/MainHeader.tsx(1 hunks)src/components/common/Modal/ConfirmModal.tsx(1 hunks)src/components/common/Modal/PopupContainer.tsx(1 hunks)src/components/group/CompletedGroupModal.tsx(0 hunks)src/components/group/GroupCard.tsx(3 hunks)src/components/group/MyGroupModal.tsx(8 hunks)src/components/memory/MemoryAddButton/MemoryAddButton.tsx(3 hunks)src/components/pollwrite/PollCreationSection.tsx(1 hunks)src/mocks/aiwrite.mock.ts(1 hunks)src/pages/aiwrite/AIWrite.tsx(1 hunks)src/pages/group/CreateGroup.tsx(1 hunks)src/pages/group/Group.tsx(1 hunks)src/pages/index.tsx(2 hunks)src/stores/usePopupStore.ts(1 hunks)src/types/record.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/components/group/CompletedGroupModal.tsx
🧰 Additional context used
🧬 Code graph analysis (9)
src/api/record/getAiUsage.ts (2)
src/types/record.ts (2)
ApiResponse(88-93)AiUsageData(82-85)src/api/index.ts (1)
apiClient(7-13)
src/components/common/BookSearchBottomSheet/BookSearchTabs.tsx (1)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.styled.ts (1)
TabContainer(104-109)
src/api/record/createAiReview.ts (2)
src/types/record.ts (2)
ApiResponse(88-93)CreateAiReviewData(76-79)src/api/index.ts (1)
apiClient(7-13)
src/components/common/MainHeader.tsx (1)
src/components/common/IconButton.tsx (1)
IconButton(3-7)
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/api/record/getAiUsage.ts (1)
getAiUsage(8-13)
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)
src/components/common/BookSearchBottomSheet/useBookSearch.ts (1)
useBookSearch(7-303)
src/pages/aiwrite/AIWrite.tsx (3)
src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/api/record/createAiReview.ts (1)
createAiReview(8-13)src/styles/global/global.ts (2)
colors(4-53)typography(56-77)
src/components/group/MyGroupModal.tsx (1)
src/pages/searchBook/SearchBook.styled.ts (2)
EmptyTitle(209-214)EmptySubText(216-220)
🪛 ast-grep (0.39.9)
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 (23)
src/mocks/aiwrite.mock.ts (1)
1-19: LGTM! 목 데이터 정의가 적절합니다.AI 독서감상문 기능을 위한 샘플 데이터가 잘 작성되었습니다. 일부 내용이 반복되는 것으로 보이지만, 더 긴 콘텐츠를 테스트하기 위한 의도적인 구성으로 판단됩니다.
src/components/common/MainHeader.tsx (1)
42-44: LGTM! 헤더 로직이 간소화되었습니다.그룹 관련 버튼을 제거하고 'home' 타입일 때만 사용자 찾기 버튼을 표시하도록 변경한 것이 적절합니다. 코드가 더 명확해졌습니다.
src/components/common/BookSearchBottomSheet/BookSearchTabs.tsx (2)
8-8: LGTM! 선택적 그룹 탭 표시 기능이 잘 구현되었습니다.
showGroupTab옵셔널 prop을 추가하여 유연성을 제공하면서도, 기본값을true로 설정하여 하위 호환성을 유지한 점이 좋습니다.
17-21: LGTM! 조건부 렌더링이 적절합니다.
showGroupTab플래그를 사용한 조건부 렌더링이 깔끔하게 구현되었습니다.src/api/record/index.ts (1)
7-8: LGTM! 새로운 API 모듈이 올바르게 export되었습니다.AI 리뷰 관련 API 함수들이 barrel export 패턴에 맞게 잘 추가되었습니다.
src/stores/usePopupStore.ts (1)
17-18: LGTM! 모달 텍스트 커스터마이징 지원이 추가되었습니다.
confirmText와cancelText옵셔널 속성을 추가하여 확인 모달의 버튼 텍스트를 유연하게 설정할 수 있게 되었습니다. 옵셔널 타입으로 정의하여 기존 코드와의 호환성도 유지됩니다.src/pages/index.tsx (2)
43-43: LGTM! AIWrite 컴포넌트가 올바르게 import되었습니다.
68-68: LGTM! 새로운 AI 작성 페이지 라우트가 추가되었습니다.
aiwrite/:roomId경로가 기존 라우팅 패턴에 맞게 잘 추가되었습니다.src/api/record/createAiReview.ts (2)
1-5: LGTM! API 응답 타입이 올바르게 정의되었습니다.
CreateAiReviewResponse타입이ApiResponse<CreateAiReviewData>패턴을 따라 일관성 있게 정의되었습니다.
8-13: LGTM! API 함수가 깔끔하게 구현되었습니다.
createAiReview함수가 간결하게 구현되었으며, 에러 처리는 호출부에서 담당하는 표준 패턴을 따르고 있습니다. 주석으로 제공된 사용 예시도 도움이 됩니다.src/components/common/Modal/PopupContainer.tsx (1)
44-46: 리뷰 코멘트는 부정확합니다. 요청된stopPropagation()처리가 이미 구현되어 있습니다.검증 결과,
ConfirmModal컴포넌트의 14번 줄에서e.stopPropagation()이handleContainerClick핸들러에 정확히 구현되어 있으며, 이 핸들러는 모든 모달 콘텐츠를 감싸는<Container>에 연결되어 있습니다. 이는 오버레이 클릭 시 모달이 닫히고 모달 내부 콘텐츠 클릭 시는 닫히지 않는 올바른 패턴입니다. 현재 구현이 의도된 동작을 정확히 수행하고 있으므로 추가 조치가 필요 없습니다.Likely an incorrect or invalid review comment.
src/pages/group/CreateGroup.tsx (1)
249-249: LGTM! 논리적으로 적절한 변경입니다.모임 생성 페이지에서 그룹 탭을 숨기는 것은 UX 관점에서 합리적입니다. 사용자가 새 모임을 만들 때는 저장된 책이나 검색한 책을 선택하는 것이 더 적합하며, 기존 모임의 책을 표시할 필요가 없습니다.
src/types/record.ts (1)
75-85: LGTM! 타입 정의가 명확합니다.AI 독서감상문 기능을 위한 타입 정의가 적절하게 추가되었습니다. 필드명과 주석이 명확하여 가독성이 좋습니다.
src/components/common/BookSearchBottomSheet/useBookSearch.ts (2)
7-7: LGTM! 후방 호환성이 유지됩니다.
showGroupTab매개변수가 기본값true로 설정되어 기존 코드에 영향을 주지 않습니다.
254-256: LGTM! 불필요한 API 호출을 방지합니다.
showGroupTab이false일 때 그룹 책 데이터를 로드하지 않도록 가드 조건이 추가되어 효율적입니다.src/pages/group/Group.tsx (1)
106-106: LGTM! 컴포넌트가 간소화되었습니다.
MainHeader의 사용이 단순화되어 가독성이 향상되었습니다. 완료된 그룹 모달 관련 로직이 제거되면서 일관된 UI 흐름이 유지됩니다.src/api/record/getAiUsage.ts (1)
8-13: LGTM! API 모듈이 프로젝트 패턴을 따릅니다.구현이 다른 API 모듈과 일관된 패턴을 따르고 있으며, 주석의 사용 예시가 호출자에게 적절한 에러 처리 방법을 안내합니다.
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx (1)
18-18: LGTM! 프롭 전파가 적절히 구현되었습니다.
showGroupTab프롭이 컴포넌트 인터페이스에서 하위 컴포넌트와 훅까지 일관되게 전달되고 있으며, 기본값true로 후방 호환성이 유지됩니다.Also applies to: 21-21, 41-41, 113-113
src/components/group/GroupCard.tsx (3)
16-16: LGTM! 완료된 그룹 지원이 추가되었습니다.
isCompleted프롭이 적절히 추가되어 완료된 그룹의 표시 방식을 제어할 수 있습니다.Also applies to: 20-23
40-44: LGTM! 조건부 렌더링이 명확합니다.완료된 그룹의 경우 최대 인원수를 숨기고 "명"만 표시하는 것이 UX 관점에서 합리적입니다. 모집이 완료되었으므로 최대 인원 정보가 더 이상 필요하지 않습니다.
45-55: LGTM! 마감일 표시 로직이 적절합니다.완료된 그룹에서는 모집 마감일 정보가 더 이상 의미가 없으므로 이를 숨기는 것이 적절합니다.
src/components/common/Modal/ConfirmModal.tsx (2)
5-12: LGTM! 텍스트 커스터마이징이 잘 구현되었습니다.
confirmText와cancelText프롭이 적절한 기본값('예','아니요')과 함께 추가되어, 다양한 사용 사례에 맞게 버튼 텍스트를 조정할 수 있습니다.
13-18: LGTM! 이벤트 전파 방지가 적절합니다.
handleContainerClick에서stopPropagation()을 호출하여 모달 외부 클릭 시 의도하지 않은 닫힘을 방지합니다.
| const { recordCount, recordReviewCount } = result.data; | ||
|
|
||
| // 기록이 2개 미만인 경우 에러 표시 | ||
| if (recordCount < 2) { | ||
| openSnackbar({ | ||
| message: `독후감 생성을 위해서는 최소 2개의 기록이 필요합니다. 현재 기록 개수: ${recordCount}`, | ||
| variant: 'top', | ||
| isError: true, | ||
| onClose: () => {}, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| // 잔여 횟수가 5회 이상인 경우 (이미 5회 모두 사용) | ||
| if (recordReviewCount >= 5) { | ||
| openSnackbar({ | ||
| message: '사용자의 독후감 작성 수가 5회를 초과했습니다.', | ||
| variant: 'top', | ||
| isError: true, | ||
| onClose: () => {}, | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| // 모달 표시 | ||
| openConfirm({ | ||
| title: 'AI 독서감상문 생성 (Beta)', | ||
| disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${recordReviewCount}/5)`, | ||
| confirmText: '확인', | ||
| cancelText: '취소', | ||
| onConfirm: () => { | ||
| closePopup(); | ||
| navigate(`/aiwrite/${currentRoomId}`); | ||
| console.log('AI 독서 감상문 생성 시작 - roomId:', currentRoomId); | ||
| }, | ||
| }); | ||
| } else { |
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.
잔여 횟수 안내 문구 수정 필요
Line 94에서 recordReviewCount 값을 그대로 노출하면서 “잔여 이용횟수”라고 안내하고 있습니다. recordReviewCount가 누적 사용 횟수라서, 첫 사용 전에도 “잔여 이용횟수 : 0/5”로 보여 사용자에게 더 이상 쓸 수 없다고 오해를 줄 수 있습니다. 실제 잔여 횟수(5 - recordReviewCount)로 계산해 표기하도록 보정해주세요.
다음과 같이 잔여 횟수를 별도로 계산하면 혼란을 줄일 수 있습니다.
if (result.isSuccess) {
- const { recordCount, recordReviewCount } = result.data;
+ const { recordCount, recordReviewCount } = result.data;
+ const remainingCount = Math.max(0, 5 - recordReviewCount);
@@
openConfirm({
title: 'AI 독서감상문 생성 (Beta)',
- disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${recordReviewCount}/5)`,
+ disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${remainingCount}/5)`,📝 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 { recordCount, recordReviewCount } = result.data; | |
| // 기록이 2개 미만인 경우 에러 표시 | |
| if (recordCount < 2) { | |
| openSnackbar({ | |
| message: `독후감 생성을 위해서는 최소 2개의 기록이 필요합니다. 현재 기록 개수: ${recordCount}`, | |
| variant: 'top', | |
| isError: true, | |
| onClose: () => {}, | |
| }); | |
| return; | |
| } | |
| // 잔여 횟수가 5회 이상인 경우 (이미 5회 모두 사용) | |
| if (recordReviewCount >= 5) { | |
| openSnackbar({ | |
| message: '사용자의 독후감 작성 수가 5회를 초과했습니다.', | |
| variant: 'top', | |
| isError: true, | |
| onClose: () => {}, | |
| }); | |
| return; | |
| } | |
| // 모달 표시 | |
| openConfirm({ | |
| title: 'AI 독서감상문 생성 (Beta)', | |
| disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${recordReviewCount}/5)`, | |
| confirmText: '확인', | |
| cancelText: '취소', | |
| onConfirm: () => { | |
| closePopup(); | |
| navigate(`/aiwrite/${currentRoomId}`); | |
| console.log('AI 독서 감상문 생성 시작 - roomId:', currentRoomId); | |
| }, | |
| }); | |
| } else { | |
| const { recordCount, recordReviewCount } = result.data; | |
| const remainingCount = Math.max(0, 5 - recordReviewCount); | |
| // 기록이 2개 미만인 경우 에러 표시 | |
| if (recordCount < 2) { | |
| openSnackbar({ | |
| message: `독후감 생성을 위해서는 최소 2개의 기록이 필요합니다. 현재 기록 개수: ${recordCount}`, | |
| variant: 'top', | |
| isError: true, | |
| onClose: () => {}, | |
| }); | |
| return; | |
| } | |
| // 잔여 횟수가 5회 이상인 경우 (이미 5회 모두 사용) | |
| if (recordReviewCount >= 5) { | |
| openSnackbar({ | |
| message: '사용자의 독후감 작성 수가 5회를 초과했습니다.', | |
| variant: 'top', | |
| isError: true, | |
| onClose: () => {}, | |
| }); | |
| return; | |
| } | |
| // 모달 표시 | |
| openConfirm({ | |
| title: 'AI 독서감상문 생성 (Beta)', | |
| disc: `기록장에서 작성한 기록을 기반으로<br/>독서감상문을 생성하시겠어요?<br/>(서비스 내 잔여 이용횟수 : ${remainingCount}/5)`, | |
| confirmText: '확인', | |
| cancelText: '취소', | |
| onConfirm: () => { | |
| closePopup(); | |
| navigate(`/aiwrite/${currentRoomId}`); | |
| console.log('AI 독서 감상문 생성 시작 - roomId:', currentRoomId); | |
| }, | |
| }); | |
| } else { |
🤖 Prompt for AI Agents
In src/components/memory/MemoryAddButton/MemoryAddButton.tsx around lines 67 to
103, the UI displays recordReviewCount as "잔여 이용횟수" which is actually the used
count; compute remainingCount = Math.max(0, 5 - recordReviewCount) and use
remainingCount in the modal/disc text (e.g., "(서비스 내 잔여 이용횟수 :
${remainingCount}/5)") so users see actual remaining uses; ensure the
calculation is done before building the openConfirm payload and keep the
existing guard that prevents use when recordReviewCount >= 5.
| const fetchAiReview = async () => { | ||
| if (!roomId) return; | ||
|
|
||
| try { | ||
| const result = await createAiReview(Number(roomId)); | ||
|
|
||
| if (result.isSuccess) { | ||
| setAiContent(result.data.content); | ||
| } else { | ||
| openSnackbar({ | ||
| message: result.message, | ||
| variant: 'top', | ||
| isError: true, | ||
| onClose: () => {}, | ||
| }); | ||
| navigate(`/rooms/${roomId}/memory`); | ||
| } | ||
| } catch (error) { | ||
| console.error('AI 독서감상문 생성 실패:', error); |
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.
roomId 누락 시 로딩이 종료되지 않습니다
Line 21에서 roomId가 없으면 바로 return해 버립니다. 이 경우 finally 블록이 실행되지 않아 isLoading이 영원히 true로 남고, 사용자는 스피너에서 빠져나오지 못합니다. 예외 상황에서도 로딩을 해제하고 필요 시 안내 후 뒤로 돌려보내는 처리가 필요합니다.
다음과 같이 조기 리턴 전에 로딩을 해제하고 사용자에게 안내하면 좋겠습니다.
const fetchAiReview = async () => {
- if (!roomId) return;
+ if (!roomId) {
+ setIsLoading(false);
+ openSnackbar({
+ message: '유효하지 않은 접근입니다.',
+ variant: 'top',
+ isError: true,
+ onClose: () => {},
+ });
+ navigate(-1);
+ return;
+ }🤖 Prompt for AI Agents
In src/pages/aiwrite/AIWrite.tsx around lines 20 to 38, the early return when
roomId is falsy exits before clearing the loading state so isLoading stays true;
modify the function to clear loading and inform the user instead of returning
immediately — either set isLoading to false and call openSnackbar with an
explanatory message and navigate back (e.g., to rooms list) before returning, or
wrap the fetch logic in try/finally and ensure the finally block always sets
isLoading to false; implement one of these fixes so the spinner is always
cleared and the user is notified when roomId is missing.
📝작업 내용
ver 1.4.0 배포 (AI 독서감상문 기능 추가 및 기타 QA 반영)
Summary by CodeRabbit
릴리스 노트
새 기능
개선 사항