feat: 오늘의 한마디 조회 API 연동 구현#153
Conversation
- getDailyGreeting API 함수 생성 (src/api/rooms/getDailyGreeting.ts) - TodayWords 컴포넌트에 실제 API 데이터 연동 - 무한 스크롤 및 페이지네이션 구현 (nextCursor 기반) - 로딩 상태 및 에러 처리 추가 - 프로필 이미지 지원 기능 추가 - 타입 정의 개선 (Message 인터페이스에 profileImageUrl, isWriter 필드 추가) - 새 메시지 작성 후 자동 새로고침 기능 - 개발용 토글 버튼으로 실제/더미 데이터 전환 가능
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough방 생성의 “오늘의 인사” 데이터를 불러오기 위한 GET API 래퍼를 추가하고, 관련 타입을 정의했습니다. TodayWords 페이지는 해당 API를 사용해 초기 로드와 무한 스크롤 로드를 구현했습니다. 메시지/아바타 렌더링 로직과 타입을 갱신했으며, 일일 전송 제한 및 오류 처리 분기를 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant TW as TodayWords Page
participant API as getDailyGreeting()
participant AC as apiClient
participant S as Server
U->>TW: 페이지 진입
TW->>API: getDailyGreeting({roomId, cursor?})
API->>AC: GET /rooms/{roomId}/daily-greeting
AC->>S: HTTP GET
S-->>AC: 200 { todayCommentList, nextCursor, isLast }
AC-->>API: response.data
API-->>TW: DailyGreetingData
TW->>TW: 변환(TodayCommentItem → Message)
TW-->>U: MessageList 렌더/빈 상태/로딩 표시
sequenceDiagram
participant U as User Scrolls
participant TW as TodayWords Page
participant API as getDailyGreeting()
participant AC as apiClient
participant S as Server
U->>TW: 바닥 근처 스크롤
TW->>TW: isLast 확인 / 중복 로드 방지
TW->>API: getDailyGreeting({roomId, cursor: nextCursor})
API->>AC: GET /rooms/{roomId}/daily-greeting
AC->>S: HTTP GET
S-->>AC: 200 { todayCommentList, nextCursor, isLast }
AC-->>API: response.data
API-->>TW: DailyGreetingData
TW->>TW: 메시지 추가/상태 갱신
TW-->>U: 리스트 하단 갱신/로딩 스피너 종료
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (11)
src/types/today.ts (2)
12-22: API-UI 매핑 용어 확인 요청 (datevstimestamp)
TodayCommentItem.date가Message.timestamp로 그대로 매핑됩니다. TodayWords에서 일자 비교는toLocaleDateString('ko-KR')포맷(YYYY.MM.DD)을 사용하므로, 백엔드에서 내려주는date포맷이 동일한지 확인 필요합니다. 불일치 시 일일 전송 횟수 계산이 오작동할 수 있습니다.
24-27: 중복 타입 정의 제거 제안
DailyGreetingData를 정의했지만 API 모듈(getDailyGreeting.ts)에서는 동일 구조를 인라인으로 재정의하고 있습니다. 단일 소스 타입으로 재사용하면 유지보수성이 좋아집니다.다음과 같이 API 모듈에서 재사용을 제안합니다:
- import { type TodayCommentItem } from '../../types/today'; + import { type TodayCommentItem, type DailyGreetingData } from '../../types/today'; - export interface DailyGreetingResponse { + export interface DailyGreetingResponse { isSuccess: boolean; code: number; message: string; - data: { - todayCommentList: TodayCommentItem[]; - nextCursor: string; - isLast: boolean; - }; + data: DailyGreetingData; }src/components/today-words/MessageList/MessageList.styled.ts (1)
28-39: 아바타 이미지 렌더링 추가(LGTM) + URL 인용(quoting) 권장Background 이미지 렌더링은 좋습니다. 다만 URL에 특수문자가 포함될 때 CSS 파싱 이슈를 줄이기 위해 따옴표로 감싸는 것을 권장합니다.
다음과 같이 수정을 제안합니다:
- background-image: ${props => props.profileImageUrl ? `url(${props.profileImageUrl})` : 'none'}; + background-image: ${props => props.profileImageUrl ? `url("${props.profileImageUrl}")` : 'none'};src/components/today-words/MessageList/MessageList.tsx (1)
101-106: alert 대신 일관된 토스트/스낵바 사용 권장전역 UI 피드백 패턴과 일관성을 위해
alert대신 상위의 스낵바/바텀시트를 재사용하는 방식을 권장합니다. 추후 삭제 API 연동 시에도 동일 UI 패턴을 사용할 수 있습니다.src/api/rooms/getDailyGreeting.ts (1)
5-14: 응답 타입 정의의 단일화 제안여기서 응답
data구조를 인라인으로 재정의하는 대신DailyGreetingData타입을 재사용하면 타입 드리프트를 방지할 수 있습니다. (동일 제안은 types 파일 코멘트에 상세)src/pages/today-words/TodayWords.tsx (6)
19-19: 사용하지 않는 ref 및 타입 정리 제안
messageListRef와 관련 타입 import가 현재 사용되지 않습니다. 불필요한 코드 제거로 가독성과 번들 크기를 소폭 개선할 수 있습니다.다음과 같이 제거를 제안합니다:
- import type { MessageListRef } from '../../components/today-words/MessageList/MessageList'; + // (unused) MessageListRef import 제거 - const messageListRef = useRef<MessageListRef>(null); + // (unused) ref 제거 - <MessageList - ref={messageListRef} - messages={messages} - /> + <MessageList messages={messages} />Also applies to: 296-299, 6-7
34-45: 일일 작성 수 계산의 포맷 의존성클라이언트에서 생성한
today문자열과 서버item.date포맷이 다르면 카운트가 틀어질 수 있습니다. 안전하게 하려면:
- 서버가
YYYY.MM.DD로 내려주도록 계약을 명시하거나,item.date를 Date로 파싱 후 동일 포맷 함수로 변환해 비교,- 혹은 서버에서 오늘 작성 개수를 별도 필드로 내려받아 사용하는 방식을 고려해 주세요.
85-89: parseInt에 radix 명시 권장암묵적 10진수에 의존하지 말고 기수(10)를 명시해 안전성을 높이세요.
- roomId: parseInt(roomId), + roomId: parseInt(roomId, 10),
75-84: 의존성 배열 보강으로 stale 클로저 방지
loadMessages의 의존성에openSnackbar가 누락되어 있고, 이를 사용하는 훅/콜백에서도loadMessages가 의존성에서 빠져 있습니다. 훅 규칙을 준수하면 재렌더/파라미터 변경 시 최신 함수를 안전하게 참조할 수 있습니다.다음과 같이 수정하세요:
- const loadMessages = useCallback(async (cursor?: string, isRefresh = false) => { + const loadMessages = useCallback(async (cursor?: string, isRefresh = false) => { // ... - }, [roomId]); + }, [roomId, openSnackbar]); - const loadMoreMessages = useCallback(() => { + const loadMoreMessages = useCallback(() => { if (!isLoadingMore && !isLast && nextCursor && roomId) { loadMessages(nextCursor); } - }, [isLoadingMore, isLast, nextCursor, roomId]); + }, [isLoadingMore, isLast, nextCursor, roomId, loadMessages]); - useEffect(() => { + useEffect(() => { if (roomId && !hasInitiallyLoaded) { loadMessages(undefined, true); } - }, [roomId, hasInitiallyLoaded]); + }, [roomId, hasInitiallyLoaded, loadMessages]);Also applies to: 142-143
158-171: 무한 스크롤 호출 제어 강화 제안현재 스크롤 이벤트는 연속으로 트리거될 수 있어 미세한 중복 호출 여지가 있습니다. 실제 문제는 크지 않지만, 성능/안정성을 위해 다음을 고려해 주세요:
- 스크롤 핸들러에 throttle/debounce 적용,
- 또는 IntersectionObserver로 하단 센티널 관찰.
Also applies to: 145-149
186-195: 클라이언트 측 일일 제한 UX 보완 포인트서버에서 400으로 차단하는 로직이 이미 있으므로, 클라이언트 측 선제 차단은 UX용 보조 장치입니다. 단, 현재 목록이 완전하지 않으면(스크롤로 더 불러오지 않은 경우) 과소/과대 차단될 수 있습니다. 가능하다면 서버에서 “오늘 내가 작성한 개수”를 추가로 내려받아 사용해 정확도를 높여 주세요.
Also applies to: 255-263
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
src/api/rooms/getDailyGreeting.ts(1 hunks)src/components/today-words/MessageList/MessageList.styled.ts(1 hunks)src/components/today-words/MessageList/MessageList.tsx(5 hunks)src/pages/today-words/TodayWords.tsx(6 hunks)src/types/today.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/components/today-words/MessageList/MessageList.styled.ts (1)
src/styles/global/global.ts (1)
semanticColors(80-153)
src/api/rooms/getDailyGreeting.ts (2)
src/types/today.ts (1)
TodayCommentItem(13-22)src/api/index.ts (1)
apiClient(7-14)
src/pages/today-words/TodayWords.tsx (6)
src/types/today.ts (2)
TodayCommentItem(13-22)Message(1-10)src/api/rooms/getDailyGreeting.ts (1)
getDailyGreeting(22-33)src/api/rooms/createDailyGreeting.ts (1)
createDailyGreeting(22-41)src/components/today-words/MessageList/MessageList.styled.ts (1)
MessageList(4-7)src/pages/today-words/TodayWords.styled.ts (1)
ContentArea(17-22)src/components/today-words/MessageInput.styled.ts (1)
MessageInput(62-90)
src/components/today-words/MessageList/MessageList.tsx (1)
src/components/today-words/MessageList/MessageList.styled.ts (1)
UserAvatar(28-39)
🔇 Additional comments (6)
src/types/today.ts (1)
8-10: Message 타입 확장(LGTM)
profileImageUrl?,isWriter?추가로 UI 요구사항을 잘 반영했습니다. 하위 호환성도 유지됩니다.src/components/today-words/MessageList/MessageList.tsx (2)
115-117: 내 메시지 판별 변경(LGTM)
isWriter를 통한 판별은 서버 신뢰 모델과 맞고, 닉네임/ID 비교보다 안전합니다.
131-132: 아바타 prop 전달(LGTM)
profileImageUrl전달로 스타일 컴포넌트와 타입 확장이 잘 연결되었습니다.src/api/rooms/getDailyGreeting.ts (1)
22-33: API 호출 래퍼 기본기 충실(LGTM)
- 선택적
cursor파라미터 처리 적절- 에러 로깅 후 재전파로 상위에서 컨텍스트별 처리 가능
src/pages/today-words/TodayWords.tsx (2)
53-72: API→UI 매핑(LGTM) + 포맷 합의 확인매핑 자체는 적절합니다. 다만
timestamp: item.date가 아래 일일 제한 계산의 날짜 문자열(ko-KR포맷)과 동일한지 확인 필요합니다. 불일치 시 같은 날 메시지가 누락되어 5회 제한 검증이 부정확해질 수 있습니다.
288-305: 로딩 상태 UI(LGTM)초기 로딩/추가 로딩 상태 분리가 명확하고, 스피너 위치도 적절합니다.
| // 먼저 모든 메시지를 시간순으로 정렬 (아래로 올수록 최신) | ||
| // ID를 기준으로 정렬 (ID가 클수록 최신) | ||
| const sortedMessages = messages.sort((a, b) => parseInt(a.id) - parseInt(b.id)); | ||
|
|
There was a problem hiding this comment.
상태 배열을 직접 변형하는 정렬 로직(치명적) — 불변성 위반
messages.sort(...)는 state 배열을 제자리(in-place)에서 변형합니다. 이 컴포넌트는 상위에서 받은 배열 참조(initialMessages)를 그대로 state에 저장하므로, 상위 컴포넌트의 state까지 의도치 않게 변형될 수 있습니다. 이는 예측 불가능한 렌더링 버그를 유발합니다.
다음과 같이 복사 후 정렬하도록 수정하세요. 함께 parseInt에 radix 지정 및 NaN 대비도 권장합니다.
- const sortedMessages = messages.sort((a, b) => parseInt(a.id) - parseInt(b.id));
+ const sortedMessages = [...messages].sort((a, b) => {
+ const aNum = parseInt(a.id, 10);
+ const bNum = parseInt(b.id, 10);
+ if (Number.isNaN(aNum) || Number.isNaN(bNum)) {
+ // 숫자 변환 실패 시 createdAt으로 폴백(오래된 → 최신)
+ return a.createdAt.getTime() - b.createdAt.getTime();
+ }
+ return aNum - bNum; // ID가 클수록 최신 → 오름차순 정렬 시 아래로 갈수록 최신
+ });📝 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.
| // 먼저 모든 메시지를 시간순으로 정렬 (아래로 올수록 최신) | |
| // ID를 기준으로 정렬 (ID가 클수록 최신) | |
| const sortedMessages = messages.sort((a, b) => parseInt(a.id) - parseInt(b.id)); | |
| // 먼저 모든 메시지를 시간순으로 정렬 (아래로 올수록 최신) | |
| // ID를 기준으로 정렬 (ID가 클수록 최신) | |
| const sortedMessages = [...messages].sort((a, b) => { | |
| const aNum = parseInt(a.id, 10); | |
| const bNum = parseInt(b.id, 10); | |
| if (Number.isNaN(aNum) || Number.isNaN(bNum)) { | |
| // 숫자 변환 실패 시 createdAt으로 폴백(오래된 → 최신) | |
| return a.createdAt.getTime() - b.createdAt.getTime(); | |
| } | |
| return aNum - bNum; // ID가 클수록 최신 → 오름차순 정렬 시 아래로 갈수록 최신 | |
| }); |
🤖 Prompt for AI Agents
In src/components/today-words/MessageList/MessageList.tsx around lines 73 to 76,
the code calls messages.sort(...) which mutates the original state array; make a
non-mutating copy first (e.g., use [...messages] or Array.from(messages]) and
sort that copy, and replace usages to use the sorted copy; also call parseInt
with an explicit radix (parseInt(x, 10)) and guard against NaN by providing a
numeric fallback (e.g., Number(x) || 0 or isNaN checks) when computing the sort
keys.
#️⃣ 연관된 이슈
#106
📝 작업 내용
이번 PR에서는 오늘의 한마디 기능의 조회 API를 완전히 연동하고, 사용자 경험을 개선하는 다양한 기능들을 구현했습니다.
🕸️ 주요 작업 내용
1. 오늘의 한마디 조회 API 연동
getDailyGreetingAPI 함수를 새로 생성하여 백엔드 API와 연동했습니다.2. 무한 스크롤 및 페이지네이션 구현
nextCursor기반의 커서 페이지네이션을 구현하여 대용량 데이터를 효율적으로 로드할 수 있게 했습니다.3. 사용자 인터페이스 개선
creatorProfileImageUrl을 활용합니다.isWriter필드를 활용하여 내가 쓴 메시지와 다른 사람의 메시지를 정확히 구분합니다.4. 메시지 관리 기능 개선
5. 하루 5개 작성 제한 로직 구현
6. 타입 안정성 및 에러 처리 강화
profileImageUrl,isWriter등의 새로운 필드를 추가했습니다.timeAgo표시 오류를 수정하여 "NaN일 전" 대신 정확한 시간 정보를 표시합니다.기술적 구현 세부사항
postDate필드가 이미 계산된 상대 시간 문자열임을 파악하고 이를 그대로 활용attendanceCheckId기준으로 변경하여 정확한 시간순 정렬 구현Summary by CodeRabbit