diff --git a/src/_apis/detail/get-crew-detail.ts b/src/_apis/crew/crew-detail-apis.ts similarity index 100% rename from src/_apis/detail/get-crew-detail.ts rename to src/_apis/crew/crew-detail-apis.ts diff --git a/src/_apis/detail/get-gathering-list.ts b/src/_apis/crew/crew-gathering-list-apis.ts similarity index 100% rename from src/_apis/detail/get-gathering-list.ts rename to src/_apis/crew/crew-gathering-list-apis.ts diff --git a/src/_apis/detail/get-gathering-detail.ts b/src/_apis/detail/get-gathering-detail.ts deleted file mode 100644 index 8ca0dc31..00000000 --- a/src/_apis/detail/get-gathering-detail.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { fetchApi } from '@/src/utils/api'; -import { GatheringDetailType } from '@/src/types/gathering-data'; - -export async function GetGatheringDetail( - crewId: number, - gatheringId: number, -): Promise { - const url = `/api/crews/${crewId}/gatherings/${gatheringId}`; - - const response = await fetchApi<{ data: GatheringDetailType }>(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - return response.data; -} diff --git a/src/_apis/detail/get-review-list.ts b/src/_apis/detail/get-review-list.ts deleted file mode 100644 index 0a55e0d3..00000000 --- a/src/_apis/detail/get-review-list.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { fetchApi } from '@/src/utils/api'; -import { CrewReview } from '@/src/types/review'; - -export interface ReviewRateInfo { - totalReviewCount: number; - averageRate: number; - ratingsData: Array<{ score: number; count: number }>; -} - -export interface ReviewListData { - info: ReviewRateInfo; - data: CrewReview[]; - totalItems: number; - totalPages: number; - currentPage: number; -} - -export async function getReviewList(page: number, limit: number): Promise { - const response = await fetchApi('/crewReviews', { - method: 'GET', - }); - - // 데이터가 비어 있는 경우 기본값 반환 - if (!response || response.length === 0) { - return { - info: { - totalReviewCount: 0, - averageRate: 0, - ratingsData: [5, 4, 3, 2, 1].map((score) => ({ score, count: 0 })), - }, - data: [], - totalItems: 0, - totalPages: 0, - currentPage: page, - }; - } - - // 페이지네이션 적용 - const startIndex = (page - 1) * limit; - const paginatedData = response.slice(startIndex, startIndex + limit); - - // 통계 정보 생성 - const totalReviewCount = response.length; // 리뷰 개수 - const averageRate = - totalReviewCount > 0 - ? response.reduce((sum, review) => sum + review.rate, 0) / totalReviewCount - : 0; - - const ratingsData = [5, 4, 3, 2, 1].map((score) => ({ - score, - count: response.filter((review) => review.rate === score).length, - })); - - const info: ReviewRateInfo = { - totalReviewCount, - averageRate, - ratingsData, - }; - - return { - info, - data: paginatedData, - totalItems: response.length, - totalPages: Math.ceil(response.length / limit), - currentPage: page, - }; -} diff --git a/src/_apis/gathering/gathering-detail-apis.ts b/src/_apis/gathering/gathering-detail-apis.ts new file mode 100644 index 00000000..3830233a --- /dev/null +++ b/src/_apis/gathering/gathering-detail-apis.ts @@ -0,0 +1,54 @@ +import { fetchApi } from '@/src/utils/api'; +import { GatheringDetailType } from '@/src/types/gathering-data'; + +// NOTE: 약속 디테일 불러오기 +export async function GetGatheringDetail( + crewId: number, + gatheringId: number, +): Promise { + const url = `/api/crews/${crewId}/gatherings/${gatheringId}`; + + const response = await fetchApi<{ data: GatheringDetailType }>(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.data; +} + +// NOTE: 약속 참여 +export async function JoinGathering(crewId: number, gatheringId: number): Promise { + const url = `/api/crews/${crewId}/gatherings/${gatheringId}/join`; + + await fetchApi(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); +} + +// NOTE: 약속 취소(host) +export async function CancelGathering(crewId: number, gatheringId: number): Promise { + const url = `/api/crews/${crewId}/gatherings/${gatheringId}`; + + await fetchApi(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); +} + +// NOTE: 약속 참여 취소(참여자) +export async function LeaveGathering(crewId: number, gatheringId: number): Promise { + const url = `/api/crews/${crewId}/gatherings/${gatheringId}/leave`; + + await fetchApi(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); +} diff --git a/src/_queries/detail/crew-detail-queries.ts b/src/_queries/crew/crew-detail-queries.ts similarity index 74% rename from src/_queries/detail/crew-detail-queries.ts rename to src/_queries/crew/crew-detail-queries.ts index 65d44914..e203945e 100644 --- a/src/_queries/detail/crew-detail-queries.ts +++ b/src/_queries/crew/crew-detail-queries.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { getCrewDetail } from '@/src/_apis/detail/get-crew-detail'; +import { getCrewDetail } from '@/src/_apis/crew/crew-detail-apis'; export function useGetCrewDetailQuery(id: number) { return useQuery({ diff --git a/src/_queries/detail/gathering-list-queries.ts b/src/_queries/crew/gathering-list-queries.ts similarity index 80% rename from src/_queries/detail/gathering-list-queries.ts rename to src/_queries/crew/gathering-list-queries.ts index dda91c30..166b8707 100644 --- a/src/_queries/detail/gathering-list-queries.ts +++ b/src/_queries/crew/gathering-list-queries.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { getGatheringList } from '@/src/_apis/detail/get-gathering-list'; +import { getGatheringList } from '@/src/_apis/crew/crew-gathering-list-apis'; import { GatheringType } from '@/src/types/gathering-data'; export function useGetGatheringListQuery(id: number) { diff --git a/src/_queries/detail/gathering-detail-queries.ts b/src/_queries/gathering/gathering-detail-queries.ts similarity index 77% rename from src/_queries/detail/gathering-detail-queries.ts rename to src/_queries/gathering/gathering-detail-queries.ts index ad5fe8da..293c2aac 100644 --- a/src/_queries/detail/gathering-detail-queries.ts +++ b/src/_queries/gathering/gathering-detail-queries.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { GetGatheringDetail } from '@/src/_apis/detail/get-gathering-detail'; +import { GetGatheringDetail } from '@/src/_apis/gathering/gathering-detail-apis'; export function useGetGatheringDetailQuery(crewId: number, gatheringId: number) { return useQuery({ diff --git a/src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx b/src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx index e67dd8bc..35b3a01a 100644 --- a/src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx +++ b/src/app/(crew)/crew/_components/gathering-detail-modal/container.tsx @@ -1,5 +1,12 @@ 'use client'; +import { + CancelGathering, + JoinGathering, + LeaveGathering, +} from '@/src/_apis/gathering/gathering-detail-apis'; +import { ApiError } from '@/src/utils/api'; +import Toast from '@/src/components/common/toast'; import { GatheringDetailType } from '@/src/types/gathering-data'; import GatheringDetailModalPresenter from './presenter'; @@ -9,22 +16,50 @@ export interface GatheringDetailModalContainerProps { data: GatheringDetailType; } +// NOTE: 테스트는 로그인 후 토큰이 안담겨서 추후 진행하겠습니다! + export default function GatheringDetailModalContainer({ opened, close, data, }: GatheringDetailModalContainerProps) { - const handleJoin = () => { - // TODO : 모임 참여하기 API 연결 - close(); + const showToast = (message: string, type: 'success' | 'error' | 'warning') => { + Toast({ message, type }); }; - const handleExit = () => { - // TODO : 모임 탈퇴하기 API 연결 - close(); + + const handleJoin = async () => { + try { + await JoinGathering(data.crewId, data.id); + showToast('약속에 참여했습니다.', 'success'); + close(); + } catch (error) { + if (error instanceof ApiError) { + showToast(`참여 중 에러 발생: ${error.message}`, 'error'); + } + } }; - const handleDelete = () => { - // TODO : 모임 삭제하기 API 연결 - close(); + + const handleExit = async () => { + try { + await LeaveGathering(data.crewId, data.id); + close(); + } catch (error) { + if (error instanceof ApiError) { + showToast(`참여 취소 중 에러 발생: ${error.message}`, 'error'); + } + } + }; + + const handleDelete = async () => { + try { + await CancelGathering(data.crewId, data.id); + showToast('약속을 삭제했습니다.', 'success'); + close(); + } catch (error) { + if (error instanceof ApiError) { + showToast(`약속 삭제 중 에러 발생: ${error.message}`, 'error'); + } + } }; return ( diff --git a/src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx b/src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx index 14d61f83..17a55a50 100644 --- a/src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx +++ b/src/app/(crew)/crew/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx @@ -55,6 +55,7 @@ export const ModalWithUser = Template.bind({}); ModalWithUser.args = { opened: false, data: { + crewId: 1, id: 1, title: '신나는 운동...즐거운..코딩..', introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', @@ -97,6 +98,7 @@ export const ModalWithCaptain = Template.bind({}); ModalWithCaptain.args = { opened: false, data: { + crewId: 1, id: 2, title: '신나는 운동...즐거운..코딩..', introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', @@ -139,6 +141,7 @@ export const ModalWithCrew = Template.bind({}); ModalWithCrew.args = { opened: false, data: { + crewId: 1, id: 3, title: '아침 타임 에너지 요가', introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', diff --git a/src/app/(crew)/crew/detail/[id]/_components/detail-crew-section.tsx b/src/app/(crew)/crew/detail/[id]/_components/detail-crew-section.tsx index e8fc67e6..f432dd53 100644 --- a/src/app/(crew)/crew/detail/[id]/_components/detail-crew-section.tsx +++ b/src/app/(crew)/crew/detail/[id]/_components/detail-crew-section.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useGetCrewDetailQuery } from '@/src/_queries/detail/crew-detail-queries'; +import { useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries'; import { ApiError } from '@/src/utils/api'; import DetailCrewCard from '@/src/components/common/crew-list/detail-crew-card'; diff --git a/src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx b/src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx index 63bd41f9..89c86615 100644 --- a/src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx +++ b/src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useGetGatheringListQuery } from '@/src/_queries/detail/gathering-list-queries'; +import { useGetGatheringListQuery } from '@/src/_queries/crew/gathering-list-queries'; import GatheringCardCarousel from '@/src/components/gathering-list/gathering-card-carousel'; interface GatheringListSectionProps { diff --git a/src/app/(crew)/crew/detail/[id]/_components/review-section.tsx b/src/app/(crew)/crew/detail/[id]/_components/review-section.tsx index f432705f..056f7ee2 100644 --- a/src/app/(crew)/crew/detail/[id]/_components/review-section.tsx +++ b/src/app/(crew)/crew/detail/[id]/_components/review-section.tsx @@ -1,38 +1,23 @@ 'use client'; import { useEffect, useState } from 'react'; -import { ReviewListData, getReviewList } from '@/src/_apis/detail/get-review-list'; import CrewReviewList from './crew-review-list'; import RatingDisplay from './rating-display'; export default function CrewReviewSection() { - const [reviewData, setReviewData] = useState(null); - const [currentPage, setCurrentPage] = useState(1); - const limit = 5; - - useEffect(() => { - async function fetchReviewData() { - const data = await getReviewList(currentPage, limit); - setReviewData(data); - } - fetchReviewData(); - }, [currentPage]); - - const handlePageChange = (page: number) => setCurrentPage(page); - - if (!reviewData) return
Loading...
; + // TODO: review 추후 추가 return (
- + {/* */}
- + /> */}
); } diff --git a/src/app/(crew)/crew/detail/[id]/page.tsx b/src/app/(crew)/crew/detail/[id]/page.tsx index 3f0f863f..2753ac93 100644 --- a/src/app/(crew)/crew/detail/[id]/page.tsx +++ b/src/app/(crew)/crew/detail/[id]/page.tsx @@ -1,4 +1,4 @@ -import { getGatheringList } from '@/src/_apis/detail/get-gathering-list'; +import { getGatheringList } from '@/src/_apis/crew/crew-gathering-list-apis'; import CreateGathering from './_components/create-gathering'; import DetailCrewSection from './_components/detail-crew-section'; import GatheringListSection from './_components/gathering-list-section'; diff --git a/src/components/common/gathering-card/container.tsx b/src/components/common/gathering-card/container.tsx index 962dae82..cc4000f1 100644 --- a/src/components/common/gathering-card/container.tsx +++ b/src/components/common/gathering-card/container.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { useDisclosure } from '@mantine/hooks'; -import { useGetGatheringDetailQuery } from '@/src/_queries/detail/gathering-detail-queries'; +import { useGetGatheringDetailQuery } from '@/src/_queries/gathering/gathering-detail-queries'; import { ApiError } from '@/src/utils/api'; import GatheringDetailModalContainer from '@/src/app/(crew)/crew/_components/gathering-detail-modal/container'; import Toast from '@/src/components/common/toast'; diff --git a/src/components/common/gathering-card/scheduled-gathering-card/container.tsx b/src/components/common/gathering-card/scheduled-gathering-card/container.tsx index 1b3915ce..fa0fb373 100644 --- a/src/components/common/gathering-card/scheduled-gathering-card/container.tsx +++ b/src/components/common/gathering-card/scheduled-gathering-card/container.tsx @@ -24,6 +24,7 @@ export default function ScheduledGatheringCardContainer({ // TODO: modalData 연결 // const { data: modalData } = useQuery(useGetGatheringQuery()); const dummyModalData = { + crewId: 7, id: 1, title: '신나는 운동...즐거운..코딩..', introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', diff --git a/src/types/gathering-data.d.ts b/src/types/gathering-data.d.ts index f7c77420..70f56e9c 100644 --- a/src/types/gathering-data.d.ts +++ b/src/types/gathering-data.d.ts @@ -12,6 +12,7 @@ export interface GatheringType { liked: boolean; } export interface GatheringDetailType extends GatheringType { + crewId: number; introduce: string; gatheringCaptain: boolean; participant: boolean;