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
34 changes: 34 additions & 0 deletions src/api/rooms/getJoinedRooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { apiClient } from '../index';

// 가입한 방 목록 응답 데이터 타입
export interface JoinedRoomItem {
roomId: number;
bookImageUrl: string;
roomTitle: string;
memberCount: number;
userPercentage: number;
}

export interface JoinedRoomsResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
roomList: JoinedRoomItem[];
nickname: string;
page: number;
size: number;
last: boolean;
first: boolean;
};
}

export const getJoinedRooms = async (page: number = 1): Promise<JoinedRoomsResponse> => {
try {
const response = await apiClient.get<JoinedRoomsResponse>(`/rooms/home/joined?page=${page}`);
return response.data;
} catch (error) {
console.error('가입한 방 목록 조회 API 오류:', error);
throw error;
}
};
44 changes: 44 additions & 0 deletions src/api/rooms/getMyRooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { apiClient } from '../index';

export type RoomType = 'playingAndRecruiting' | 'recruiting' | 'playing' | 'expired';

// 방 데이터 타입
export interface Room {
roomId: number;
bookImageUrl: string;
roomName: string;
recruitCount: number;
memberCount: number;
endDate: string;
type: string;
}

// 내 방 조회 응답 타입
export interface MyRoomsResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
roomList: Room[];
nextCursor: string;
isLast: boolean;
};
}

export const getMyRooms = async (
type: RoomType = 'playingAndRecruiting',
cursor: string | null = null,
): Promise<MyRoomsResponse> => {
try {
const params = new URLSearchParams();
params.append('type', type);
if (cursor) {
params.append('cursor', cursor);
}
const response = await apiClient.get<MyRoomsResponse>(`/rooms/my?${params.toString()}`);
return response.data;
} catch (error) {
console.error('내 방 조회 API 오류:', error);
throw error;
}
};
49 changes: 49 additions & 0 deletions src/api/rooms/getRoomDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { apiClient } from '../index';

// 방 상세 정보 응답 타입
export interface RoomDetailResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
isHost: boolean;
isJoining: boolean;
roomId: number;
roomName: string;
roomImageUrl: string;
isPublic: boolean;
progressStartDate: string;
progressEndDate: string;
recruitEndDate: string;
category: string;
roomDescription: string;
memberCount: number;
recruitCount: number;
isbn: string;
bookImageUrl: string;
bookTitle: string;
authorName: string;
bookDescription: string;
publisher: string;
recommendRooms: RecommendRoom[];
};
}

export interface RecommendRoom {
roomId: number;
roomImageUrl: string;
roomName: string;
memberCount: number;
recruitCount: number;
recruitEndDate: string;
}

export const getRoomDetail = async (roomId: number): Promise<RoomDetailResponse> => {
try {
const response = await apiClient.get<RoomDetailResponse>(`/rooms/${roomId}/recruiting`);
return response.data;
} catch (error) {
console.error('방 상세 정보 조회 API 오류:', error);
throw error;
}
};
33 changes: 33 additions & 0 deletions src/api/rooms/getRoomsByCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { apiClient } from '../index';

// 방 목록 응답 데이터 타입
export interface RoomItem {
roomId: number;
bookImageUrl: string;
roomName: string;
recruitCount: number;
memberCount: number;
deadlineDate: string;
}

export interface RoomsResponse {
isSuccess: boolean;
code: number;
message: string;
data: {
deadlineRoomList: RoomItem[];
popularRoomList: RoomItem[];
};
}

export const getRoomsByCategory = async (category: string): Promise<RoomsResponse> => {
try {
const response = await apiClient.get<RoomsResponse>(
`/rooms?category=${encodeURIComponent(category)}`,
);
return response.data;
} catch (error) {
console.error('방 목록 조회 API 오류:', error);
throw error;
}
};
185 changes: 104 additions & 81 deletions src/components/group/CompletedGroupModal.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,58 @@
import { useState, useEffect } from 'react';
import styled from '@emotion/styled';
import leftArrow from '../../assets/common/leftArrow.svg';
import type { Group } from './MyGroupBox';
import { GroupCard } from './GroupCard';
import TitleHeader from '../common/TitleHeader';
import { Modal, Overlay } from './Modal.styles';
import { getMyRooms, type Room } from '@/api/rooms/getMyRooms';
import { colors, typography } from '@/styles/global/global';

interface CompletedGroupModalProps {
onClose: () => void;
}

const dummyCompletedGroups: Group[] = [
{
id: '1',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 1,
genre: '문학',
},
{
id: '2',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 2,
genre: '문학',
},
{
id: '3',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 3,
genre: '문학',
},
{
id: '4',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 4,
genre: '문학',
},
{
id: '5',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 4,
genre: '과학·IT',
},
{
id: '6',
title: '시집만 읽는 사람들 3월',
userName: 'hoho2',
participants: 15,
maximumParticipants: 30,
coverUrl:
'https://marketplace.canva.com/EAF9zlwqylI/1/0/1003w/canva-%EB%B2%A0%EC%9D%B4%EC%A7%80-%EC%A3%BC%ED%99%A9-%EA%B7%80%EC%97%BD%EA%B3%A0-%EB%AF%B8%EB%8B%88%EB%A9%80%ED%95%9C-%EC%9D%BC%EB%9F%AC%EC%8A%A4%ED%8A%B8-e%EB%B6%81-%EC%9C%84%EB%A1%9C-%EC%A2%8B%EC%9D%80%EA%B8%80-%EC%B1%85%ED%91%9C%EC%A7%80-zrZ6hI8_IWo.jpg',
deadLine: 4,
genre: '과학·IT',
},
];
const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
const [rooms, setRooms] = useState<Room[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

const userName = '00';
const convertRoomToGroup = (room: Room): Group => {
return {
id: room.roomId.toString(),
title: room.roomName,
userName: '',
participants: room.memberCount,
maximumParticipants: room.recruitCount,
coverUrl: room.bookImageUrl,
deadLine: 0,
isOnGoing: false,
};
};

const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
useEffect(() => {
const fetchCompletedRooms = async () => {
try {
setIsLoading(true);
setError(null);
const response = await getMyRooms('expired', null);
if (response.isSuccess) {
setRooms(response.data.roomList);
} else {
setError(response.message);
}
} catch (error) {
console.error('완료된 방 목록 조회 실패:', error);
setError('완료된 방 목록을 불러오는데 실패했습니다.');
} finally {
setIsLoading(false);
}
};

fetchCompletedRooms();
}, []);

const convertedGroups = rooms.map(convertRoomToGroup);
return (
<Overlay>
<Modal>
Expand All @@ -89,11 +61,20 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
leftIcon={<img src={leftArrow} alt="뒤로 가기" />}
onLeftClick={onClose}
/>
<Text>{userName}님이 참여했던 모임방들을 확인해보세요.</Text>
<Content>
{dummyCompletedGroups.map(group => (
<GroupCard key={group.id} group={group} type={'modal'}></GroupCard>
))}
<Text>00님이 참여했던 모임방들을 확인해보세요.</Text>
<Content isEmpty={!isLoading && !error && convertedGroups.length === 0}>
{isLoading ? (
<LoadingMessage>로딩 중...</LoadingMessage>
) : error ? (
<ErrorMessage>{error}</ErrorMessage>
) : convertedGroups.length > 0 ? (
convertedGroups.map(group => <GroupCard key={group.id} group={group} type={'modal'} />)
) : (
<EmptyState data-empty="true">
<EmptyTitle>완료된 모임방이 없어요</EmptyTitle>
<EmptySubText>아직 완료된 모임방이 없습니다.</EmptySubText>
</EmptyState>
)}
</Content>
</Modal>
</Overlay>
Expand All @@ -103,21 +84,63 @@ const CompletedGroupModal = ({ onClose }: CompletedGroupModalProps) => {
export default CompletedGroupModal;

const Text = styled.p`
font-size: var(--font-size-medium01);
font-weight: var(--font-weight-regular);
color: var(--color-white);
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
color: ${colors.white};
margin: 96px 20px 20px 20px;
`;

const Content = styled.div`
const Content = styled.div<{ isEmpty?: boolean }>`
display: grid;
gap: 20px;
overflow-y: auto;
overflow-y: ${({ isEmpty }) => (isEmpty ? 'visible' : 'auto')};
padding: 0 20px;

flex: 1;
grid-template-columns: 1fr;

@media (min-width: 584px) {
grid-template-columns: 1fr 1fr;
}
`;

const LoadingMessage = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: ${colors.white};
font-size: ${typography.fontSize.base};
`;

const ErrorMessage = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: #ff6b6b;
font-size: ${typography.fontSize.base};
`;

const EmptyState = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px 20px;
color: ${colors.grey[100]};
text-align: center;
height: 100%;
`;

const EmptyTitle = styled.p`
font-size: ${typography.fontSize.lg};
font-weight: ${typography.fontWeight.semibold};
margin-bottom: 8px;
color: ${colors.white};
`;

const EmptySubText = styled.p`
font-size: ${typography.fontSize.sm};
font-weight: ${typography.fontWeight.regular};
color: ${colors.grey[100]};
`;
Loading