Skip to content

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Nov 11, 2025

📝작업 내용

ver 1.4.0 배포 (AI 독서감상문 기능 추가 및 기타 QA 반영)

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • AI 기반 독서 감상문 자동 생성 기능 추가
    • 생성된 감상문 복사 기능 추가
    • 모임 목록에 "완료" 탭 추가
    • 확인 모달의 버튼 텍스트 커스터마이징 가능
  • 개선 사항

    • UI 및 네비게이션 간소화
    • 모임 검색 시 그룹 탭 표시 옵션 추가

heeeeyong and others added 30 commits October 13, 2025 18:21
[FEAT] 인플루언서 추천 피드 - 지현
ljh130334 and others added 16 commits October 29, 2025 23:06
[API] AI 독서감상문 생성 및 AI 이용횟수 조회 API 연동
feat: 모임 생성 시 책 선택에서 모임 책 탭 숨김 처리
모임 메인 페이지 사용성 개선 2차 작업
fix: 완료된 모임방 접근 개선
This reverts commit 154caf7, reversing
changes made to 8d5414e.
Revert "Merge pull request #284 from THIP-TextHip/feat/influencer"
fix: 사용자 아이콘 변경 및 투표 생성 가이드 메세지 수정
@heeeeyong heeeeyong self-assigned this Nov 11, 2025
@heeeeyong heeeeyong added the 🌏 Deploy 배포 관련 label Nov 11, 2025
@vercel
Copy link

vercel bot commented Nov 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
thip Ready Ready Preview Comment Nov 11, 2025 1:32pm

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

AI 독서 감상문 생성 기능 추가 및 그룹 완료 상태 관리 개선. 새로운 AI 리뷰 API 통합, AIWrite 페이지 컴포넌트, 메모리 추가 버튼의 AI 기능, 북 검색의 그룹 탭 조건부 표시, 그룹 카드의 완료 상태 표시, 모달 텍스트 커스터마이제이션 지원.

Changes

경주 / 파일(들) 변경 요약
AI 리뷰 API 및 타입
src/api/record/createAiReview.ts, src/api/record/getAiUsage.ts, src/api/record/index.ts, src/types/record.ts
AI 리뷰 생성 및 사용량 조회 API 헬퍼 함수 추가, CreateAiReviewData 및 AiUsageData 타입 정의, 새 함수들을 index에서 내보냄
AIWrite 페이지 및 목 데이터
src/pages/aiwrite/AIWrite.tsx, src/mocks/aiwrite.mock.ts, src/pages/index.tsx
AI 생성 감상문 표시 페이지 신규 생성, 로딩/성공/실패 상태 처리, 복사 기능, 백 네비게이션 포함, aiwrite/:roomId 라우트 추가, 목 데이터 추가
MemoryAddButton AI 통합
src/components/memory/MemoryAddButton/MemoryAddButton.tsx
AI 독서 감상문 생성 드롭다운 항목 추가, 사용량 확인 후 조건부 실행 (최소 2개 기록, 최대 5회 사용), 확인 모달 및 에러 스낵바 처리
북 검색 그룹 탭 제어
src/components/common/BookSearchBottomSheet/BookSearchBottomSheet.tsx, src/components/common/BookSearchBottomSheet/BookSearchTabs.tsx, src/components/common/BookSearchBottomSheet/useBookSearch.ts, src/pages/group/CreateGroup.tsx
showGroupTab 속성 추가 및 스레딩, 그룹 탭 조건부 렌더링, 초기 데이터 로딩 시 그룹 데이터 fetching 제어
그룹 완료 상태 관리
src/components/group/GroupCard.tsx, src/components/group/MyGroupModal.tsx, src/components/group/CompletedGroupModal.tsx, src/pages/group/Group.tsx
GroupCard에 isCompleted 속성 추가 (완료 상태 시 마감일 숨김), MyGroupModal에 "완료" 탭 추가, 완료 상태 필터링 및 라우팅 지원, CompletedGroupModal 제거
모달 및 헤더 업데이트
src/components/common/MainHeader.tsx, src/components/common/Modal/ConfirmModal.tsx, src/components/common/Modal/PopupContainer.tsx, src/stores/usePopupStore.ts
MainHeader에서 groupDoneLogo 제거 및 조건부 로직 단순화, ConfirmModal에 confirmText/cancelText 커스터마이제이션 지원, PopupContainer에 클릭 핸들러 추가, usePopupStore에 새 선택적 필드 추가
기타 UI 변경
src/components/pollwrite/PollCreationSection.tsx
투표 입력 플레이스홀더 텍스트 "내용"에서 "제목"으로 변경

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

주의 깊게 검토해야 할 영역:

  • MyGroupModal.tsx: "완료" 탭 추가로 인한 상태 관리 및 라우팅 로직 변경이 복잡하며, 기존 로드 더 로직과의 상호작용 확인 필요
  • MemoryAddButton.tsx: AI 사용량 검증 로직 (최소/최대 제약), 모달 흐름, 네비게이션이 정확히 연결되었는지 검증 필요
  • AIWrite.tsx: 새로운 페이지 컴포넌트로 에러 처리, 로딩 상태, 클립보드 복사 기능의 견고성 확인
  • useBookSearch.ts 및 관련 컴포넌트들: showGroupTab 파라미터 스레딩이 모든 호출 지점에서 올바르게 전파되는지 확인
  • GroupCard.tsx 및 MyGroupModal.tsx: 완료 상태 표시 로직과 마감일/모집 상태 표시의 상호작용 검증
  • CompletedGroupModal 제거: 기존 Group.tsx에서의 제거가 완전한지, 고아 임포트가 없는지 확인

Possibly related PRs

Suggested labels

✨ Feature, 📬 API

Suggested reviewers

  • ho0010
  • ljh130334

Poem

🐰 AI의 마법으로 감상문을 만들고,
완료된 모임은 새로운 탭에서 반짝이네.
탭을 숨기고, 상태를 관리하며,
사용량을 세심히 체크하여
사용자 경험을 다듬는구나! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive PR 제목이 구체적이지 않고 모호합니다. 'develop -> main 머지'는 단순 브랜치 작업을 나타낼 뿐, 실제 변경 사항의 핵심(AI 독서감상문 기능 추가 및 QA 반영)을 명확하게 전달하지 못합니다. 제목을 'AI 독서감상문 기능 추가 및 UI 개선'과 같이 실질적인 변경 사항을 반영하도록 수정하세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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: 플레이스홀더 텍스트 개선이 적절합니다.

"투표 내용"에서 "투표 제목"으로 변경하여 사용자가 입력해야 할 내용을 더 명확하게 전달합니다.

선택 사항: 향후 리팩토링 시, contentonContentChange prop 이름을 titleonTitleChange로 변경하는 것을 고려해보세요. 이를 통해 UI 텍스트와 코드 네이밍의 일관성을 높일 수 있습니다.

src/components/common/Modal/ConfirmModal.tsx (1)

20-20: dangerouslySetInnerHTML 패턴은 현재 안전하지만, 방어적 개선을 고려하세요.

검증 결과: disc prop의 모든 사용처를 조사한 결과, 현재 코드는 XSS 취약점이 없습니다. 대부분 개발자가 작성한 하드코딩된 문자열이고, 유일한 동적 값인 recordReviewCount는 숫자 타입으로 타입 안전성이 보장됩니다.

그러나 주의할 점: dangerouslySetInnerHTML 패턴은 구조적으로 위험합니다. 향후 사용자 입력이나 API 응답을 disc에 추가할 때 안전성이 보장되지 않습니다.

선택사항:

  • <br/> 태그가 필요한 경우, JSX 요소나 CSS로 대체 검토
  • 방어적 목적으로 DOMPurify 라이브러리 추가 고려
  • disc prop에 오직 안전한 소스만 전달한다는 규칙을 문서화
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 674aed9 and f4a52ab.

⛔ Files ignored due to path filters (3)
  • src/assets/common/infoIcon_white.svg is excluded by !**/*.svg
  • src/assets/feed/people.svg is excluded by !**/*.svg
  • src/assets/memory/ai.svg is 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! 모달 텍스트 커스터마이징 지원이 추가되었습니다.

confirmTextcancelText 옵셔널 속성을 추가하여 확인 모달의 버튼 텍스트를 유연하게 설정할 수 있게 되었습니다. 옵셔널 타입으로 정의하여 기존 코드와의 호환성도 유지됩니다.

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 호출을 방지합니다.

showGroupTabfalse일 때 그룹 책 데이터를 로드하지 않도록 가드 조건이 추가되어 효율적입니다.

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! 텍스트 커스터마이징이 잘 구현되었습니다.

confirmTextcancelText 프롭이 적절한 기본값('예', '아니요')과 함께 추가되어, 다양한 사용 사례에 맞게 버튼 텍스트를 조정할 수 있습니다.


13-18: LGTM! 이벤트 전파 방지가 적절합니다.

handleContainerClick에서 stopPropagation()을 호출하여 모달 외부 클릭 시 의도하지 않은 닫힘을 방지합니다.

Comment on lines +67 to +103
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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

잔여 횟수 안내 문구 수정 필요
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.

Suggested change
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.

Comment on lines +20 to +38
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);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🌏 Deploy 배포 관련

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants