feat: 오늘의 한마디 작성 API 연동#124
Conversation
Walkthrough방 생성별 “오늘의 한마디” 작성 API를 추가하고, TodayWords 페이지에서 해당 API를 사용하도록 전송 흐름을 서버 연동으로 변경했다. MessageInput에 disabled 속성을 도입해 전송 중 입력/전송을 비활성화했다. 라우팅은 /today-words/:roomId 형태의 동적 파라미터를 사용하도록 수정했다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant TW as TodayWords Page
participant MI as MessageInput
participant API as createDailyGreeting()
participant S as Server
U->>MI: 입력 후 전송 클릭/엔터
MI-->>TW: onSend(content)
TW->>TW: roomId 확인 / isSubmitting 설정
TW->>API: createDailyGreeting(roomId, content)
API->>S: POST /rooms/{roomId}/daily-greeting
S-->>API: { isSuccess, data.attendanceCheckId, ... }
API-->>TW: CreateDailyGreetingResponse
TW->>TW: 메시지 목록에 추가, 스크롤, isSubmitting 해제
TW-->>MI: disabled=false로 해제, 입력 초기화
TW-->>U: Snackbar로 결과 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ 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 (
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
src/components/today-words/MessageInput.tsx (1)
48-55: 실패 시 입력값 소실 가능성: 입력 초기화는 부모에서 성공 시점에만 수행하는 것이 안전합니다.현재 MessageInput이 onSend 직후 즉시 onChange('')로 값을 비우고 있어, 서버 에러가 나도 사용자의 입력이 사라집니다. 입력값 초기화는 부모(상위 컴포넌트)에서 “성공 시”에만 수행하도록 위임하는 편이 UX에 안전합니다. 아래처럼 이 컴포넌트에서는 즉시 비우는 코드를 제거해 주세요. TodayWords에서는 이미 성공 시 setInputValue('')를 호출하고 있습니다.
const handleSend = () => { - if (!value.trim() || disabled) return; - onSend(); - onChange(''); + if (!value.trim() || disabled) return; + onSend(); if (inputRef.current) { inputRef.current.style.height = 'auto'; } };
🧹 Nitpick comments (7)
src/components/today-words/MessageInput.tsx (2)
90-101: 접근성(a11y) 보강: 비활성/로딩 상태를 ARIA로도 노출하세요.disabled UI 상태를 스크린리더에도 일관되게 전달하려면 aria 속성을 함께 지정하는 것이 좋습니다.
<StyledMessageInput ref={inputRef} placeholder={disabled ? '전송 중...' : placeholder} // disabled일 때 placeholder 변경 value={value} onChange={handleInputChange} onKeyDown={handleKeyDown} onCompositionStart={() => !disabled && setIsComposing(true)} // disabled일 때는 composing 상태 변경 안함 onCompositionEnd={() => !disabled && setIsComposing(false)} rows={1} disabled={disabled} + aria-disabled={disabled} + aria-busy={disabled} style={{ opacity: disabled ? 0.6 : 1, cursor: disabled ? 'not-allowed' : 'text', }} /> <SendButton onClick={handleSend} disabled={!value.trim() || disabled} active={!!value.trim() && !disabled} + aria-disabled={!value.trim() || disabled} style={{ opacity: disabled ? 0.6 : 1, cursor: disabled ? 'not-allowed' : 'pointer', }} >Also applies to: 103-111
98-101: 스타일 책임 일원화 제안(선택): inline style 대신 styled로 상태 스타일을 흡수하세요.opacity/cursor를 inline으로 지정하면 테마 변경이나 상태 추가 시 분산 관리가 발생합니다. styled-components로 disabled/active 상태를 모두 반영하도록 옮기면 일관성과 유지보수성이 좋아집니다. 필요하시면 스타일 파일까지 포함한 리팩터를 도와드릴게요.
Also applies to: 107-110
src/api/rooms/createDailyGreeting.ts (3)
27-30: 전송 전 content를 안전하게 trim해 주세요.UI 단에서 trim을 하더라도 API 레이어에서 한 번 더 보수적으로 처리해두면 예외 케이스에 안전합니다.
const requestBody: CreateDailyGreetingRequest = { - content, + content: content.trim(), };
37-41: 에러 처리 타이핑/로깅 개선 제안
- 현재
console.error로 즉시 로그를 남기고 예외를 재던지는데, 상위 호출부에서도 동일 로그를 남겨 중복될 수 있습니다. API 레이어에서는 로깅을 생략하고 예외만 “정확한 타입”으로 전달하는 편이 깔끔합니다.- AxiosError 제네릭을 사용해 응답 스키마를 안전하게 참조하세요.
참고 구현 예시(추가 import 필요:
import { AxiosError } from 'axios'):try { const response = await apiClient.post<CreateDailyGreetingResponse>( `/rooms/${roomId}/daily-greeting`, requestBody, ); return response.data; } catch (e) { const err = e as AxiosError<{ isSuccess?: boolean; code?: number; message?: string }>; // 필요 시 err.response?.status 등으로 상위에서 분기 throw err; }
14-19: 실패 응답 스키마 확인 필요(data 필드 존재 여부)
CreateDailyGreetingResponse에서data를 항상 필수로 두셨는데, 서버가 실패 응답(isSuccess=false)에서data를 생략한다면 런타임에서 접근 시 안전하지 않습니다. 스웨거 명세대로 실패 시에도data가 항상 존재하는지 확인해 주세요. 필요하다면data?: CreateDailyGreetingData로 변경하고 호출부에서 안전 접근이 필요합니다.src/pages/today-words/TodayWords.tsx (2)
95-119: 에러 매핑 로직에서 HTTP status도 함께 활용해 주세요.현재
response.data.code만 분기합니다. 많은 백엔드에서 HTTP status로 표준 에러를 전달하므로,response.status도 함께 확인하면 메시지 정확도가 올라갑니다.- if (error && typeof error === 'object' && 'response' in error) { - const axiosError = error as { - response?: { - data?: { - message?: string; - code?: number; - }; - }; - }; - - if (axiosError.response?.data?.message) { - errorMessage = axiosError.response.data.message; - } else if (axiosError.response?.data?.code === 400) { - errorMessage = '오늘의 한마디 작성 가능 횟수를 초과했습니다.'; - } else if (axiosError.response?.data?.code === 403) { - errorMessage = '방 접근 권한이 없습니다.'; - } else if (axiosError.response?.data?.code === 404) { - errorMessage = '존재하지 않는 방입니다.'; - } - } + if (error && typeof error === 'object' && 'response' in error) { + const axiosError = error as any; + const status: number | undefined = axiosError.response?.status; + + if (axiosError.response?.data?.message) { + errorMessage = axiosError.response.data.message; + } else if (status === 400) { + errorMessage = '오늘의 한마디 작성 가능 횟수를 초과했습니다.'; + } else if (status === 403) { + errorMessage = '방 접근 권한이 없습니다.'; + } else if (status === 404) { + errorMessage = '존재하지 않는 방입니다.'; + } + }또는 AxiosError 제네릭을 사용해
status/data접근을 타입 안전하게 만드는 것도 권장드립니다.
31-33: 중복 전송 레이스 소지(경미): 로컬 가드 추가 고려부모의 isSubmitting이 렌더에 반영되기 전 아주 짧은 타이밍에 중복 클릭이 들어갈 수 있습니다. 현재
handleSendMessage가isSubmitting을 재검사하므로 실질 위험은 낮지만, 추가로useRef기반 로컬 가드를 둬도 좋습니다. 다만 필수는 아닙니다.
📜 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 (4)
src/api/rooms/createDailyGreeting.ts(1 hunks)src/components/today-words/MessageInput.tsx(5 hunks)src/pages/index.tsx(1 hunks)src/pages/today-words/TodayWords.tsx(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/pages/today-words/TodayWords.tsx (4)
src/components/today-words/MessageList/MessageList.tsx (1)
MessageListRef(28-30)src/types/today.ts (1)
Message(1-8)src/hooks/usePopupActions.ts (1)
usePopupActions(9-35)src/api/rooms/createDailyGreeting.ts (1)
createDailyGreeting(22-41)
src/components/today-words/MessageInput.tsx (1)
src/components/today-words/MessageInput.styled.ts (1)
SendButton(92-115)
src/api/rooms/createDailyGreeting.ts (1)
src/api/index.ts (1)
apiClient(7-14)
🔇 Additional comments (3)
src/pages/index.tsx (1)
72-72: 레이거시/today-words경로 호출부 없음 확인 완료
전체 코드베이스에서/today-words단독 호출(파라미터 없이 이동) 사례가 발견되지 않았습니다.
- import, Route 정의,
navigate(/today-words/${roomId})등 모두:roomId파라미터를 포함
따라서 404 발생 우려 없이 변경 내용은 안전합니다. LGTM!src/pages/today-words/TodayWords.tsx (2)
50-71: 성공 플로우 및 메시지 구성 LGTM서버 id(attendanceCheckId) 사용, 로캘 기반 날짜 포맷, 상태 추가/입력 초기화/자동 스크롤까지 흐름이 안정적입니다. 상단 제안(입력 초기화 책임 이전)을 적용하면 실패 시 입력 유지까지 해결됩니다.
180-186: MessageInput disabled 연동은 적절합니다.전송 중 비활성화로 중복 전송을 예방하고, placeholder 변경 등 UI 피드백도 잘 연결되어 있습니다. 위 제안(입력값 초기화 책임 이전)을 적용하면 실패 시 입력 유지까지 자연스럽게 해결됩니다.
| const handleSendMessage = useCallback(async () => { | ||
| if (inputValue.trim() === '' || isSubmitting) return; | ||
|
|
||
| // 빈 상태에서 메시지를 보낼 때 실제 messages 상태를 업데이트 | ||
| if (!showMessages) { | ||
| // 새 메시지 생성 | ||
| const now = new Date(); | ||
| const newMessage: Message = { | ||
| id: Date.now().toString(), | ||
| user: 'user.01', | ||
| content: inputValue.trim(), | ||
| timestamp: now | ||
| .toLocaleDateString('ko-KR', { | ||
| year: 'numeric', | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| }) | ||
| .replace(/\. /g, '.') | ||
| .replace(/\.$/, ''), | ||
| timeAgo: '방금 전', | ||
| createdAt: now, | ||
| }; | ||
|
|
||
| // 실제 messages 상태에 추가 | ||
| setMessages(prevMessages => [...prevMessages, newMessage]); | ||
| } else { | ||
| // MessageList의 addMessage 함수 호출 (더미 데이터 상태일 때) | ||
| if (messageListRef.current) { | ||
| messageListRef.current.addMessage(inputValue.trim()); | ||
| // roomId가 없으면 에러 처리 | ||
| if (!roomId) { | ||
| openSnackbar({ | ||
| message: '방 정보를 찾을 수 없습니다.', | ||
| variant: 'top', | ||
| onClose: () => {}, | ||
| }); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
roomId 존재 체크는 OK입니다. 숫자 유효성까지 함께 검증해 주세요.
현재는 roomId 문자열 존재만 확인합니다. 숫자가 아니거나(NaN), 0/음수인 경우를 걸러 API 호출을 막아야 합니다.
아래처럼 API 호출 직전에 유효성 검증을 추가하는 것을 권장합니다.
try {
- // API 호출 - 오늘의 한마디 작성
- const response = await createDailyGreeting(parseInt(roomId), inputValue.trim());
+ // roomId 숫자 유효성 검증
+ const numericRoomId = Number(roomId);
+ if (!Number.isInteger(numericRoomId) || numericRoomId <= 0) {
+ openSnackbar({
+ message: '유효하지 않은 방 정보입니다.',
+ variant: 'top',
+ onClose: () => {},
+ });
+ return;
+ }
+
+ // API 호출 - 오늘의 한마디 작성
+ const response = await createDailyGreeting(numericRoomId, inputValue.trim());🤖 Prompt for AI Agents
In src/pages/today-words/TodayWords.tsx around lines 31 to 43, the current check
only verifies roomId exists but doesn’t validate it’s a positive number; add a
numeric validation right before the API call by converting roomId to a number
(e.g., Number or parseInt), check for NaN and that the value is greater than 0,
and if invalid call openSnackbar with an appropriate error message and return to
prevent the API request; ensure you keep the existing empty/onClose behavior and
isSubmitting guard intact.
#️⃣ 연관된 이슈
#106
📝 작업 내용
오늘의 한마디 작성 기능의 API 연동을 완료했습니다. 스웨거 명세서에 정의된 POST
/rooms/{roomId}/daily-greetingAPI를 기반으로 클라이언트와 서버 간의 통신을 구현했습니다.🕸️ 주요 작업 내용
1. API 연동 함수 구현:
src/api/rooms/createDailyGreeting.ts파일을 새로 생성하여 오늘의 한마디 작성 API 호출 함수를 구현했습니다. 요청 시 roomId와 content를 받아 서버로 전송하고, 성공 시 attendanceCheckId를 포함한 응답을 반환하도록 구현했습니다.2. TodayWords 컴포넌트 API 연동: 기존에 더미 데이터로만 작동하던 오늘의 한마디 페이지를 실제 API와 연동했습니다. URL 파라미터에서 roomId를 받아와서 해당 방의 오늘의 한마디를 작성할 수 있도록 수정했습니다. 메시지 전송 시 API를 호출하고, 성공/실패에 따른 적절한 사용자 피드백을 제공합니다.
3. 라우팅 구조 개선: Router.tsx에서 기존의
/today-words경로를/today-words/:roomId로 변경하여 방별로 구분된 오늘의 한마디 페이지에 접근할 수 있도록 했습니다.4. MessageInput 컴포넌트 개선: 메시지 입력 컴포넌트에 disabled 속성을 추가하여 API 요청 중일 때 중복 전송을 방지하고 사용자에게 전송 중 상태를 시각적으로 표시할 수 있도록 개선했습니다.
사용자 경험 개선사항
Summary by CodeRabbit