-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 진행중인 방 상세보기 및 독서메이트 조회 API 연동 구현 #122
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
Changes from all commits
b8df306
959bf05
b22d2ab
a7a2f90
d6eb173
959cfa9
7829941
842f143
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { apiClient } from '../index'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export interface RoomMember { | ||||||||||||||||||||||||||||||||||||||||||||||
| userId: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| nickname: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| aliasName: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| followerCount: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 독서메이트 조회 응답 타입 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface RoomMembersResponse { | ||||||||||||||||||||||||||||||||||||||||||||||
| isSuccess: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| code: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||
| userList: RoomMember[]; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 기존 Member 타입과 연결하기 위한 변환 함수 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface Member { | ||||||||||||||||||||||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| nickname: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| role: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| followersCount?: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| profileImageUrl?: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const convertRoomMembersToMembers = (roomMembers: RoomMember[]): Member[] => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const convertedMembers = roomMembers.map(member => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const convertedMember: Member = { | ||||||||||||||||||||||||||||||||||||||||||||||
| id: member.userId.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||
| nickname: member.nickname || '익명', | ||||||||||||||||||||||||||||||||||||||||||||||
| role: member.aliasName || '독서메이트', | ||||||||||||||||||||||||||||||||||||||||||||||
| followersCount: member.followerCount || 0, | ||||||||||||||||||||||||||||||||||||||||||||||
| profileImageUrl: member.imageUrl || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return convertedMember; | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return convertedMembers; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const getRoomMembers = async (roomId: number): Promise<RoomMembersResponse> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await apiClient.get<RoomMembersResponse>(`/rooms/${roomId}/users`); | ||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.error('독서메이트 조회 API 오류:', error); | ||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion isSuccess=false 응답 처리 누락 API가 200이어도 export const getRoomMembers = async (roomId: number): Promise<RoomMembersResponse> => {
try {
- const response = await apiClient.get<RoomMembersResponse>(`/rooms/${roomId}/users`);
- return response.data;
+ const response = await apiClient.get<RoomMembersResponse>(`/rooms/${roomId}/users`);
+ const data = response.data;
+ if (!data.isSuccess) {
+ throw new Error(`[${data.code}] ${data.message}`);
+ }
+ return data;
} catch (error) {
console.error('독서메이트 조회 API 오류:', error);
throw error;
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { apiClient } from '../index'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 투표 아이템 타입 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface VoteItem { | ||||||||||||||||||||||||||||||||||||||||||||||
| itemName: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 현재 투표 타입 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface CurrentVote { | ||||||||||||||||||||||||||||||||||||||||||||||
| content: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| page: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| isOverview: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| voteItems: VoteItem[]; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 진행중인 방 상세 정보 응답 타입 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface RoomPlayingResponse { | ||||||||||||||||||||||||||||||||||||||||||||||
| isSuccess: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| code: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||
| isHost: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| roomId: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| roomName: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| roomImageUrl: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| isPublic: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| progressStartDate: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| progressEndDate: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| category: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| categoryColor: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| roomDescription: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| memberCount: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| recruitCount: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| isbn: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| bookTitle: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| authorName: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| currentPage: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| userPercentage: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| currentVotes: CurrentVote[]; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // HotTopicSection에서 사용할 Poll 타입 (API 데이터를 변환) | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface Poll { | ||||||||||||||||||||||||||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| question: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| options: { id: string; text: string }[]; | ||||||||||||||||||||||||||||||||||||||||||||||
| pageNumber: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // API 데이터를 Poll 형태로 변환하는 함수 | ||||||||||||||||||||||||||||||||||||||||||||||
| export const convertVotesToPolls = (currentVotes: CurrentVote[]): Poll[] => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return currentVotes.map((vote, index) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||
| id: index.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||
| question: vote.content, | ||||||||||||||||||||||||||||||||||||||||||||||
| options: vote.voteItems.map((item, itemIndex) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||
| id: itemIndex.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||
| text: item.itemName, | ||||||||||||||||||||||||||||||||||||||||||||||
| })), | ||||||||||||||||||||||||||||||||||||||||||||||
| pageNumber: vote.page, | ||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const getRoomPlaying = async (roomId: number): Promise<RoomPlayingResponse> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await apiClient.get<RoomPlayingResponse>(`/rooms/${roomId}/playing`); | ||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.error('진행중인 방 상세 정보 조회 API 오류:', error); | ||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion isSuccess=false 응답 처리 누락 멤버 조회와 동일하게, export const getRoomPlaying = async (roomId: number): Promise<RoomPlayingResponse> => {
try {
- const response = await apiClient.get<RoomPlayingResponse>(`/rooms/${roomId}/playing`);
- return response.data;
+ const response = await apiClient.get<RoomPlayingResponse>(`/rooms/${roomId}/playing`);
+ const data = response.data;
+ if (!data.isSuccess) {
+ throw new Error(`[${data.code}] ${data.message}`);
+ }
+ return data;
} catch (error) {
console.error('진행중인 방 상세 정보 조회 API 오류:', error);
throw error;
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| import { useNavigate } from 'react-router-dom'; | ||
| import type { KeyboardEvent } from 'react'; | ||
| import styled from '@emotion/styled'; | ||
| import rightChevron from '../../assets/member/right-chevron.svg'; | ||
| import type { Member } from '../../mocks/members.mock'; | ||
| import type { Member } from '@/api/rooms/getRoomMembers'; | ||
| import { | ||
| Container, | ||
| MemberItem, | ||
|
|
@@ -47,7 +48,11 @@ const MemberList = ({ members, onMemberClick }: MemberListProps) => { | |
| }} | ||
| > | ||
| <ProfileSection> | ||
| <ProfileImage /> | ||
| {member.profileImageUrl ? ( | ||
| <ProfileImageWithSrc src={member.profileImageUrl} alt={`${member.nickname} 프로필`} /> | ||
| ) : ( | ||
| <ProfileImage /> | ||
| )} | ||
|
Comment on lines
+51
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 프로필 이미지 유무에 따른 아바타 크기 불일치(48px vs 36px)로 인한 레이아웃 흔들림
아바타 크기 통일(48px) 및 토큰 일관성(가능 시 colors 사용) 제안: -const ProfileImageWithSrc = styled.img`
- width: 36px;
- height: 36px;
- border-radius: 50%;
- background-color: var(--color-grey-400);
- flex-shrink: 0;
- object-fit: cover;
-`;
+const ProfileImageWithSrc = styled.img`
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background-color: var(--color-grey-400);
+ flex-shrink: 0;
+ object-fit: cover;
+`;참고: 디자인 토큰 일관성을 위해 import { colors } from '../../styles/global/global';그리고 속성 변경: - background-color: var(--color-grey-400);
+ background-color: ${colors.grey['400']};Also applies to: 71-79 🤖 Prompt for AI Agents |
||
| <MemberInfo> | ||
| <MemberName>{member.nickname}</MemberName> | ||
| <MemberRole>{member.role}</MemberRole> | ||
|
|
@@ -63,4 +68,14 @@ const MemberList = ({ members, onMemberClick }: MemberListProps) => { | |
| ); | ||
| }; | ||
|
|
||
| // 프로필 이미지가 있을 때 사용하는 스타일드 컴포넌트 | ||
| const ProfileImageWithSrc = styled.img` | ||
| width: 36px; | ||
| height: 36px; | ||
| border-radius: 50%; | ||
| background-color: var(--color-grey-400); | ||
| flex-shrink: 0; | ||
| object-fit: cover; | ||
| `; | ||
|
|
||
| export default MemberList; | ||
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.
🛠️ Refactor suggestion
필드명 변동에 견고한 매핑 구현 (followerCount/subscriberCount 모두 지원)
서버 필드명이 변동될 가능성에 대비해 두 키를 모두 안전하게 처리하는 편이 좋습니다. 아래처럼 인터페이스에
subscriberCount?를 추가하고, 변환 함수에서 nullish coalescing을 사용하세요.export interface RoomMember { userId: number; nickname: string; imageUrl: string; - aliasName: string; - followerCount: number; + aliasName: string; + followerCount?: number; + subscriberCount?: number; } export const convertRoomMembersToMembers = (roomMembers: RoomMember[]): Member[] => { - const convertedMembers = roomMembers.map(member => { - const convertedMember: Member = { - id: member.userId.toString(), - nickname: member.nickname || '익명', - role: member.aliasName || '독서메이트', - followersCount: member.followerCount || 0, - profileImageUrl: member.imageUrl || undefined, - }; - - return convertedMember; - }); - - return convertedMembers; + return roomMembers.map(member => ({ + id: member.userId.toString(), + nickname: member.nickname || '익명', + role: member.aliasName || '독서메이트', + followersCount: member.followerCount ?? member.subscriberCount ?? 0, + profileImageUrl: member.imageUrl || undefined, + })); };원하시면 간단한 단위 테스트 케이스(두 키 각각/동시에 존재/둘 다 없음)를 추가해 드릴게요.
📝 Committable suggestion
🤖 Prompt for AI Agents