Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/api/rooms/getRoomMembers.ts
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;
};
Comment on lines +30 to +44
Copy link

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

‼️ 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
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 interface RoomMember {
userId: number;
nickname: string;
imageUrl: string;
aliasName: string;
followerCount?: number;
subscriberCount?: number;
}
export const convertRoomMembersToMembers = (roomMembers: RoomMember[]): Member[] => {
return roomMembers.map(member => ({
id: member.userId.toString(),
nickname: member.nickname || '익명',
role: member.aliasName || '독서메이트',
followersCount: member.followerCount ?? member.subscriberCount ?? 0,
profileImageUrl: member.imageUrl || undefined,
}));
};
🤖 Prompt for AI Agents
In src/api/rooms/getRoomMembers.ts around lines 30 to 44, the conversion assumes
the server field is followerCount only; add robustness by updating the
RoomMember type to include optional subscriberCount? and change the mapping to
use nullish coalescing (prefer member.followerCount ?? member.subscriberCount ??
0) for followersCount; keep other fallbacks (nickname, aliasName, imageUrl)
as-is and ensure the convertedMember.followersCount uses that combined value so
the function handles either field being present, both present, or neither.


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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isSuccess=false 응답 처리 누락

API가 200이어도 isSuccess가 false일 수 있습니다. 현재는 그대로 데이터를 반환해 UI에서 혼동될 수 있으므로 명시적으로 에러를 던지도록 수정하세요.

 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

‼️ 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
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;
}
};
export const getRoomMembers = async (roomId: number): Promise<RoomMembersResponse> => {
try {
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;
}
};
🤖 Prompt for AI Agents
In src/api/rooms/getRoomMembers.ts around lines 46 to 54, the function currently
returns response.data even when the backend returns isSuccess: false; update the
success handling to check response.data.isSuccess after the GET and, if it is
false, throw an Error (or a typed error) containing the backend message or a
clear description (e.g., `throw new Error(response.data.message || 'Failed to
get room members')`) so callers receive an exception instead of invalid data;
keep the existing try/catch but ensure the thrown error is re-thrown after
logging.

72 changes: 72 additions & 0 deletions src/api/rooms/getRoomPlaying.ts
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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isSuccess=false 응답 처리 누락

멤버 조회와 동일하게, 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

‼️ 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
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;
}
};
export const getRoomPlaying = async (roomId: number): Promise<RoomPlayingResponse> => {
try {
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;
}
};
🤖 Prompt for AI Agents
In src/api/rooms/getRoomPlaying.ts around lines 64 to 72, the current
implementation returns response.data without checking response.data.isSuccess;
add the same explicit isSuccess check as used in member retrieval: after
receiving response.data, if response.data.isSuccess === false then throw an
Error (use response.data.message or a descriptive message like "Failed to get
room playing") so callers receive an exception for failed API responses; keep
the try/catch and only log/throw for unexpected network errors as currently
done.

4 changes: 2 additions & 2 deletions src/components/members/MemberList.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export const ProfileSection = styled.div`
`;

export const ProfileImage = styled.div`
width: 48px;
height: 48px;
width: 36px;
height: 36px;
border-radius: 50%;
background-color: ${colors.grey['400']};
flex-shrink: 0;
Expand Down
19 changes: 17 additions & 2 deletions src/components/members/MemberList.tsx
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,
Expand Down Expand Up @@ -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
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로필 이미지 유무에 따른 아바타 크기 불일치(48px vs 36px)로 인한 레이아웃 흔들림

ProfileImage는 48x48(참조: MemberList.styled.ts)인데, ProfileImageWithSrc는 36x36이라 목록 정렬이 틀어질 수 있습니다. 동일 크기로 통일하는 것을 권장합니다.

아바타 크기 통일(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;
+`;

참고: 디자인 토큰 일관성을 위해 background-colorcolors.grey['400']로 맞추고 싶다면 상단에 colors import가 필요합니다(선택):

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
In src/components/members/MemberList.tsx around lines 51-55 (and also apply same
change to 71-79), the avatar components use different sizes causing layout
shift: ProfileImage is 48x48 while ProfileImageWithSrc is 36x36; change
ProfileImageWithSrc to use the same 48x48 dimensions and matching styling as
ProfileImage (including border-radius and margin rules) so list alignment is
consistent. If you want token consistency, import colors from
'../../styles/global/global' and set its background-color to colors.grey['400']
(or match the existing token used by ProfileImage); ensure props like src and
alt are preserved and update any related styled-component definitions rather
than adding ad-hoc inline styles.

<MemberInfo>
<MemberName>{member.nickname}</MemberName>
<MemberRole>{member.role}</MemberRole>
Expand All @@ -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;
Loading