Skip to content

feat: 오늘의 한마디 작성 API 연동#124

Merged
ljh130334 merged 2 commits into
developfrom
feat/api-rooms-today
Aug 16, 2025
Merged

feat: 오늘의 한마디 작성 API 연동#124
ljh130334 merged 2 commits into
developfrom
feat/api-rooms-today

Conversation

@ljh130334
Copy link
Copy Markdown
Member

@ljh130334 ljh130334 commented Aug 16, 2025

#️⃣ 연관된 이슈

#106

📝 작업 내용

오늘의 한마디 작성 기능의 API 연동을 완료했습니다. 스웨거 명세서에 정의된 POST /rooms/{roomId}/daily-greeting API를 기반으로 클라이언트와 서버 간의 통신을 구현했습니다.

🕸️ 주요 작업 내용

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

  • New Features
    • 오늘의 한마디를 방별로 서버에 전송하고, 성공 시 목록에 즉시 반영됩니다.
    • 전송 결과를 스낵바로 안내하며, 오류 유형에 따른 안내 메시지를 제공합니다.
    • 전송 중 입력창과 전송 버튼이 비활성화되고 상태가 시각적으로 표시됩니다.
    • 전송 후 자동 스크롤이 유지됩니다.
    • TodayWords 경로가 /today-words/:roomId 로 변경되어 방 ID 기반 접근을 지원합니다.

@ljh130334 ljh130334 added the 📬 API 서버 API 통신 label Aug 16, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 16, 2025

Walkthrough

방 생성별 “오늘의 한마디” 작성 API를 추가하고, TodayWords 페이지에서 해당 API를 사용하도록 전송 흐름을 서버 연동으로 변경했다. MessageInput에 disabled 속성을 도입해 전송 중 입력/전송을 비활성화했다. 라우팅은 /today-words/:roomId 형태의 동적 파라미터를 사용하도록 수정했다.

Changes

Cohort / File(s) Summary
API: Daily Greeting 생성
src/api/rooms/createDailyGreeting.ts
신규 API 모듈 추가. 타입(CreateDailyGreetingRequest/Data/Response) 정의 및 createDailyGreeting(roomId, content) POST 호출 구현. 에러 로깅 후 재throw.
컴포넌트: 입력 비활성화 지원
src/components/today-words/MessageInput.tsx
MessageInput에 disabled?: boolean 추가(기본 false). disabled 시 입력/전송/엔터/조합 이벤트 억제, placeholder 및 스타일 비활성화 처리, 버튼 활성 조건 갱신.
라우팅 변경
src/pages/index.tsx
TodayWords 경로를 /today-words/:roomId로 변경.
페이지: TodayWords 서버 연동 전송
src/pages/today-words/TodayWords.tsx
useParams로 roomId 추출, createDailyGreeting 연동, isSubmitting으로 중복 전송 방지, Snackbar 기반 성공/실패 메시지, 성공 시 서버 id(attendanceCheckId)로 메시지 추가 및 스크롤, 개발 모드용 더미 전송 경로 유지, 입력 비활성화 연동.

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로 결과 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • ho0010

Poem

오늘도 토끼는 휘리릭, 키보드에 춤을 춰요 🐇
방마다 인사 한 줄, 햇살처럼 살포시 떠요 ✨
전송 중엔 살짝 쉿, 귀를 접고 기다려요…
서버에서 “OK!” 오면, 꼬리를 폴짝 들어 보여요!
오늘의 한마디, 달처럼 둥글게 안부 전해요 🌙

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/api-rooms-today

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 16, 2025

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

Project Deployment Preview Comments Updated (UTC)
thip Ready Ready Preview Comment Aug 16, 2025 6:04am

Copy link
Copy Markdown

@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: 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이 렌더에 반영되기 전 아주 짧은 타이밍에 중복 클릭이 들어갈 수 있습니다. 현재 handleSendMessageisSubmitting을 재검사하므로 실질 위험은 낮지만, 추가로 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.

📥 Commits

Reviewing files that changed from the base of the PR and between d663e1c and baa27d0.

📒 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 피드백도 잘 연결되어 있습니다. 위 제안(입력값 초기화 책임 이전)을 적용하면 실패 시 입력 유지까지 자연스럽게 해결됩니다.

Comment on lines +31 to +43
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

@ljh130334 ljh130334 merged commit 534c0c0 into develop Aug 16, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant