diff --git a/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx b/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx index 2efa077b..8836b3a7 100644 --- a/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx +++ b/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx @@ -14,13 +14,13 @@ import PublicSelect from './PublicDropdown'; const CardRightSection = ({ memberList, - isPublic, + isPublic = false, className, meetingId, showPublicSelect = false, }: { memberList: Member[]; - isPublic: boolean; + isPublic?: boolean; className?: string; meetingId: number; showPublicSelect?: boolean; @@ -61,7 +61,7 @@ const CardRightSection = ({ memberList, ); router.push( - `/my-meeting/my/user-list?meetingId=${meetingId}&type=${showPublicSelect ? 'created' : 'joined'}`, + `/my-meeting/my/user-list?meetingId=${meetingId}&type=${showPublicSelect ? 'created' : 'participated'}`, ); }; diff --git a/src/app/(user-page)/my-meeting/_features/Joined.tsx b/src/app/(user-page)/my-meeting/_features/Joined.tsx deleted file mode 100644 index dad6ab0c..00000000 --- a/src/app/(user-page)/my-meeting/_features/Joined.tsx +++ /dev/null @@ -1,191 +0,0 @@ -// 'use client'; - -// import Dropdown from '@/components/common/Dropdown'; -// import { Button } from '@/components/ui/Button'; -// import HorizonCard from '@/components/ui/HorizonCard'; -// import { Tag } from '@/components/ui/Tag'; -// import Modal from '@/components/ui/modal/Modal'; -// import Image from 'next/image'; -// import { useState } from 'react'; - -// import { Meeting, Member } from '../my-meeting/my/page'; -// import ModalProfile from './ModalProfile'; -// import ModalUserList from './ModalUserList'; - -// const CardRightSection = ({ -// memberList, -// isPublic, -// className, -// }: { -// memberList: Member[]; -// isPublic: boolean; -// className?: string; -// }) => { -// const [selectedFilter, setSelectedFilter] = useState( -// isPublic ? '공개' : '비공개', -// ); -// const [isUserListModalOpen, setIsUserListModalOpen] = useState(false); -// const handleConfirm = () => { -// setIsUserListModalOpen(false); -// }; -// const filterAreaOptions = [ -// { value: 'true', label: '공개' }, -// { value: 'false', label: '비공개' }, -// ]; - -// const [isUserProfileModalOpen, setIsUserProfileModalOpen] = useState(false); - -// const handleSecondModalConfirm = () => { -// // 가입 확인 api 연동 -// setIsUserProfileModalOpen(false); -// }; - -// const handleSecondModalCancel = () => { -// // 가입 거절 api 연동 -// setIsUserProfileModalOpen(false); -// }; -// return ( -//
-//
-//

참가 중인 멤버

-//
-// {memberList.map((member: Member) => ( -//
-// 맴버 프로필 -//

-// {member.name} -//

-//
-// -// -//
-//
-// ))} -//
-//
-// -//
-// -//
-// -// -// -// setIsUserListModalOpen(false)} -// onConfirm={handleConfirm} -// showOnly -// modalClassName="h-[590px] w-[520px] overflow-y-auto" -// > -// -// -//
-// ); -// }; - -// const Joined = ({ meetings }: { meetings: Meeting[] }) => { -// return ( -//
-// {meetings.map((meeting) => { -// return ( -//
-// {/* 데스크탑 */} -//
-// -// -// -//
- -// {/* 태블릿 */} -//
-// -// -//
- -// {/* 모바일 */} -//
-// -// -//
-//
-// ); -// })} -//
-// ); -// }; -// export default Joined; diff --git a/src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx b/src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx new file mode 100644 index 00000000..f219bd2c --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/LeaveMeetingButton.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { useQuitMeetingMutation } from '@/hooks/mutations/useMyMeetingMutation'; +import { LogOut } from 'lucide-react'; + +interface LeaveMeetingButtonProps { + meetingId: number; + className?: string; +} + +const LeaveMeetingButton = ({ + meetingId, + className = '', +}: LeaveMeetingButtonProps) => { + const { mutate: quitMeeting, isPending: isLoading } = + useQuitMeetingMutation(); + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + quitMeeting(meetingId); + }; + + return ( + + ); +}; + +export default LeaveMeetingButton; diff --git a/src/app/(user-page)/my-meeting/_features/Likes.tsx b/src/app/(user-page)/my-meeting/_features/Likes.tsx new file mode 100644 index 00000000..96f56144 --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/Likes.tsx @@ -0,0 +1,138 @@ +'use client'; + +import HorizonCard from '@/components/ui/HorizonCard'; +import useInfiniteScroll from '@/hooks/common/useInfiniteScroll'; +import { useInfiniteMyMeetingLikesQueries } from '@/hooks/queries/useMyMeetingQueries'; +import { translateCategoryNameToEng } from '@/util/searchFilter'; +import { useRouter } from 'next/navigation'; +import React from 'react'; + +import MeetingListSkeleton from './skeletons/SkeletonMeetingList'; + +const Likes = () => { + const router = useRouter(); + + const { + data: meetingData, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + } = useInfiniteMyMeetingLikesQueries(); + + const lastMeetingRef = useInfiniteScroll({ + fetchNextPage, + isFetchingNextPage, + hasNextPage: !!hasNextPage, + }); + + if (isLoading || !meetingData) { + return ; + } + + return ( +
+ {meetingData.pages.map((page, pageIdx) => ( +
+ {page.content.map((meeting) => ( +
+ {/* 데스크탑 */} +
+ + router.push( + `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`, + ) + } + key={meeting.meetingId} + title={meeting.title} + thumbnailUrl={meeting.thumbnail} + location={meeting.location} + total={meeting.maxMember} + value={meeting.memberCount} + className="flex-row" + meetingId={meeting.meetingId} + category={translateCategoryNameToEng(meeting.categoryTitle)} + isLike={true} + likesCount={meeting.likesCount} + > +
+ + {/* 태블릿 */} +
+ + router.push( + `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`, + ) + } + key={meeting.meetingId} + title={meeting.title} + thumbnailUrl={meeting.thumbnail} + location={meeting.location} + total={meeting.maxMember} + value={meeting.memberCount} + thumbnailHeight={160} + thumbnailWidth={160} + className="" + meetingId={meeting.meetingId} + category={translateCategoryNameToEng(meeting.categoryTitle)} + isLike={true} + likesCount={meeting.likesCount} + /> +
+ + {/* 모바일 */} +
+ + router.push( + `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`, + ) + } + key={meeting.meetingId} + title={meeting.title} + thumbnailUrl={meeting.thumbnail} + location={meeting.location} + total={meeting.maxMember} + value={meeting.memberCount} + thumbnailHeight={80} + thumbnailWidth={80} + className="" + meetingId={meeting.meetingId} + category={translateCategoryNameToEng(meeting.categoryTitle)} + isLike={true} + likesCount={meeting.likesCount} + /> +
+
+ ))} +
+ ))} + + {/* 무한 스크롤을 위한 별도의 Observer 요소 */} + {hasNextPage && ( +
+ )} + + {/* 추가 데이터 로딩 중 표시 */} + {isFetchingNextPage && } + + {/* 데이터가 없는 경우 표시 */} + {meetingData.pages[0].content.length === 0 && ( +
+
+

찜한 모임이 없어요.

+

원하는 모임을 찜해보세요!

+
+
+ )} +
+ ); +}; + +export default Likes; diff --git a/src/app/(user-page)/my-meeting/_features/Participated.tsx b/src/app/(user-page)/my-meeting/_features/Participated.tsx index 803b6d0b..c332341f 100644 --- a/src/app/(user-page)/my-meeting/_features/Participated.tsx +++ b/src/app/(user-page)/my-meeting/_features/Participated.tsx @@ -3,11 +3,20 @@ import HorizonCard from '@/components/ui/HorizonCard'; import useInfiniteScroll from '@/hooks/common/useInfiniteScroll'; import { useInfiniteMyMeetingParticipatedQueries } from '@/hooks/queries/useMyMeetingQueries'; +import { translateCategoryNameToEng } from '@/util/searchFilter'; +import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { IMyMeetingParticipated } from 'types/myMeeting'; import CardRightSection from './CardRightSection'; +import LeaveMeetingButton from './LeaveMeetingButton'; +import PendingStatusChip from './PendingStatusChip'; import MeetingListSkeleton from './skeletons/SkeletonMeetingList'; +interface IStatusOverlay { + meeting: IMyMeetingParticipated; +} + const Participated = () => { const router = useRouter(); @@ -32,15 +41,46 @@ const Participated = () => { return ; } - // 상세 페이지로 이동하는 핸들러 - const handleMoveDetailPage = (meetingId: number) => { - /** - * TODO - * 추후 category 수정 - */ - router.push(`/meeting/study/${meetingId}`); + // 클릭 불가능한 상태인지 확인하는 함수 (오버레이 표시 여부만 결정) + const isDisabledStatus = ( + status: IMyMeetingParticipated['myMemberStatus'], + ): boolean => { + return status === 'REJECTED' || status === 'EXPEL'; }; + // 모임 상세 페이지 URL 생성 함수 + const getMeetingDetailUrl = (meeting: IMyMeetingParticipated) => + `/meeting/${translateCategoryNameToEng(meeting.categoryTitle)}/${meeting.meetingId}`; + + const handleCardClick = (meeting: IMyMeetingParticipated) => { + router.push(getMeetingDetailUrl(meeting)); + }; + + // 오버레이 컴포넌트 (수정된 버전) + const StatusOverlay = ({ meeting }: IStatusOverlay) => ( +
+
+ {meeting.myMemberStatus === 'REJECTED' ? ( +

+ 죄송합니다. 가입이 거절된 + 모임입니다. +

+ ) : ( +

+ 더 이상 참여가 불가능한 + 모임입니다. +

+ )} + + 상세페이지 보기 + +
+
+ ); + return (
{meetingData.pages.map((page, pageIdx) => ( @@ -49,31 +89,52 @@ const Participated = () => {
{/* 데스크탑 */}
- handleMoveDetailPage(meeting.meetingId)} - key={meeting.meetingId} - title={meeting.title} - thumbnailUrl={meeting.thumbnail} - location={meeting.location} - total={meeting.maxMember} - value={meeting.memberCount} - className="flex-row" - meetingId={meeting.meetingId} - category={''} - > - + handleCardClick(meeting)} + key={meeting.meetingId} + title={meeting.title} + thumbnailUrl={meeting.thumbnail} + location={meeting.location} + total={meeting.maxMember} + value={meeting.memberCount} + className="flex-row" meetingId={meeting.meetingId} - /> - + showLikeButton={false} + category={''} + > + + + + {/* PENDING 상태일 때 승인 대기중 칩 표시 */} + {meeting.myMemberStatus === 'PENDING' && ( + + )} + + {/* APPROVED 상태일 때 모임 탈퇴하기 버튼 표시 */} + {meeting.myMemberStatus === 'APPROVED' && + !meeting.isMeetingManager && ( + + )} + + {/* 비활성화된 상태일 때 오버레이 */} + {isDisabledStatus(meeting.myMemberStatus) && ( + + )} +
{/* 태블릿 */} -
+
handleMoveDetailPage(meeting.meetingId)} + onClick={() => handleCardClick(meeting)} key={meeting.meetingId} title={meeting.title} thumbnailUrl={meeting.thumbnail} @@ -84,20 +145,36 @@ const Participated = () => { thumbnailWidth={160} className="" meetingId={meeting.meetingId} + showLikeButton={false} category={''} /> + + {meeting.myMemberStatus === 'PENDING' && ( + + )} + + {meeting.myMemberStatus === 'APPROVED' && + !meeting.isMeetingManager && ( + + )} + + {isDisabledStatus(meeting.myMemberStatus) && ( + + )}
{/* 모바일 */} -
+
handleMoveDetailPage(meeting.meetingId)} + onClick={() => handleCardClick(meeting)} key={meeting.meetingId} title={meeting.title} thumbnailUrl={meeting.thumbnail} @@ -108,14 +185,30 @@ const Participated = () => { thumbnailWidth={80} className="" meetingId={meeting.meetingId} + showLikeButton={false} category={''} /> + + {meeting.myMemberStatus === 'PENDING' && ( + + )} + + {meeting.myMemberStatus === 'APPROVED' && + !meeting.isMeetingManager && ( + + )} + + {isDisabledStatus(meeting.myMemberStatus) && ( + + )}
))} @@ -123,34 +216,18 @@ const Participated = () => { ))} {/* 무한 스크롤을 위한 별도의 Observer 요소 */} - {hasNextPage && ( -
- )} + {hasNextPage &&
} {/* 추가 데이터 로딩 중 표시 */} - {isFetchingNextPage && ( -
-
- 로딩 중... -
-
- )} + {isFetchingNextPage && } {/* 데이터가 없는 경우 표시 */} {meetingData.pages[0].content.length === 0 && ( -
- 참여한 모임이 없습니다. -
- )} - - {/* 더 이상 데이터가 없음을 표시 */} - {!hasNextPage && meetingData.pages[0].content.length > 0 && ( -
- 모든 모임을 불러왔습니다. +
+
+

내가 참여하고있는 모임이 없어요.

+

원하는 모임에 참가하세요!

+
)}
diff --git a/src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx b/src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx new file mode 100644 index 00000000..a38dff94 --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/PendingStatusChip.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { useCancelPendingMutation } from '@/hooks/mutations/useMyMeetingMutation'; +import { LogOut } from 'lucide-react'; + +interface PendingStatusChipProps { + meetingId: number; + text?: string; +} + +export const PendingStatusChip = ({ + meetingId, + text, +}: PendingStatusChipProps) => { + const { mutate: cancelPending, isPending: isLoading } = + useCancelPendingMutation(); + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + cancelPending(meetingId); + }; + + return ( +
+
+ 승인 대기중 + + . + . + . + +
+ + +
+ ); +}; + +export default PendingStatusChip; diff --git a/src/app/(user-page)/my-meeting/likes/page.tsx b/src/app/(user-page)/my-meeting/likes/page.tsx index 3ae9476e..efbfdbd8 100644 --- a/src/app/(user-page)/my-meeting/likes/page.tsx +++ b/src/app/(user-page)/my-meeting/likes/page.tsx @@ -1,5 +1,30 @@ -import NotYet from '@/components/common/NotYet'; +import { myMeetingKeys } from '@/hooks/queries/useMyMeetingQueries'; +import { + HydrationBoundary, + QueryClient, + dehydrate, +} from '@tanstack/react-query'; +import { getMyMeetingLikes } from 'service/api/mymeeting'; +import { Paginated } from 'types/meeting'; +import { IMyMeetingLikes } from 'types/myMeeting'; -export default function LikesPage() { - return ; +import Likes from '../_features/Likes'; + +export default async function LikesPage() { + const queryClient = new QueryClient(); + + await queryClient.prefetchInfiniteQuery({ + queryKey: myMeetingKeys.likes(), + queryFn: ({ pageParam }) => getMyMeetingLikes(pageParam), + getNextPageParam: (lastPage: Paginated) => + lastPage.nextCursor ?? false, + initialPageParam: 0, + }); + return ( +
+ + + +
+ ); } diff --git a/src/app/(user-page)/my-meeting/my/page.tsx b/src/app/(user-page)/my-meeting/my/page.tsx index 0c599143..8016bed8 100644 --- a/src/app/(user-page)/my-meeting/my/page.tsx +++ b/src/app/(user-page)/my-meeting/my/page.tsx @@ -13,7 +13,7 @@ import { } from 'service/api/mymeeting'; import { getBanner } from 'service/api/mypageProfile'; import { Paginated } from 'types/meeting'; -import { IMyMeetingManage } from 'types/myMeeting'; +import { IMyMeetingManage, IMyMeetingParticipated } from 'types/myMeeting'; import Created from '../_features/Created'; import Participated from '../_features/Participated'; @@ -48,7 +48,7 @@ export default async function Page({ await queryClient.prefetchInfiniteQuery({ queryKey: myMeetingKeys.participated(), queryFn: ({ pageParam }) => getMyMeetingParticipated(pageParam), - getNextPageParam: (lastPage: Paginated) => + getNextPageParam: (lastPage: Paginated) => lastPage.nextCursor ?? false, initialPageParam: 0, }); diff --git a/src/hooks/mutations/useMyMeetingMutation.ts b/src/hooks/mutations/useMyMeetingMutation.ts index 64c69cba..9c49b118 100644 --- a/src/hooks/mutations/useMyMeetingMutation.ts +++ b/src/hooks/mutations/useMyMeetingMutation.ts @@ -1,10 +1,71 @@ import { useToast } from '@/components/common/ToastContext'; +import axiosInstance from '@/lib/axios/axiosInstance'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { putExpel, putIsPublic, putMemberStatus } from 'service/api/mymeeting'; +import { deleteCancel, deleteQuit } from 'service/api/mymeeting'; +import { meetingKeys } from '../queries/useMeetingQueries'; import { myMeetingKeys } from '../queries/useMyMeetingQueries'; +// 참가중인 모임 나가기 훅 +const useQuitMeetingMutation = () => { + const { showToast } = useToast(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (meetingId: number) => deleteQuit(meetingId), + onSuccess: (_, meetingId) => { + showToast('모임에서 탈퇴했습니다.', 'success'); + queryClient.invalidateQueries({ + queryKey: myMeetingKeys.participated(), + }); + queryClient.invalidateQueries({ + queryKey: meetingKeys.detailInfo(meetingId), + }); + }, + onError: (error: AxiosError) => { + const errorData = error.response?.data as + | { data?: { request?: string } } + | undefined; + const errorMessage = errorData?.data?.request || ''; + + if ( + errorMessage.includes('Meeting manager cannot quit meeting') && + error.response?.status === 400 + ) { + showToast('주최자는 모임을 탈퇴할 수 없습니다.', 'error'); + } else { + showToast('모임 탈퇴에 실패했습니다. 다시 시도해주세요.', 'error'); + } + }, + }); +}; + +// 승인 대기중인 모임 취소 훅 +const useCancelPendingMutation = () => { + const { showToast } = useToast(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (meetingId: number) => deleteCancel(meetingId), + onSuccess: (_, meetingId) => { + showToast('승인 대기를 취소했습니다.', 'success'); + queryClient.invalidateQueries({ + queryKey: myMeetingKeys.participated(), + }); + queryClient.invalidateQueries({ + queryKey: meetingKeys.detailInfo(meetingId), + }); + }, + onError: (error: AxiosError) => { + if (error.response?.status) { + showToast('승인 대기 취소에 실패했습니다. 다시 시도해주세요.', 'error'); + } + }, + }); +}; + const useMemberStatusMutation = (meetingId: number) => { const { showToast } = useToast(); const queryClient = useQueryClient(); @@ -95,4 +156,10 @@ const useChangePublic = (meetingId: number) => { }); }; -export { useMemberStatusMutation, useExpelMutation, useChangePublic }; +export { + useMemberStatusMutation, + useExpelMutation, + useChangePublic, + useQuitMeetingMutation, + useCancelPendingMutation, +}; diff --git a/src/hooks/queries/useMyMeetingQueries.ts b/src/hooks/queries/useMyMeetingQueries.ts index 4fed671a..f1dabf15 100644 --- a/src/hooks/queries/useMyMeetingQueries.ts +++ b/src/hooks/queries/useMyMeetingQueries.ts @@ -3,14 +3,22 @@ import { getMyMeetingManage, getMyMeetingMemberProfile, } from 'service/api/mymeeting'; -import { getMyMeetingParticipated } from 'service/api/mymeeting'; +import { + getMyMeetingLikes, + getMyMeetingParticipated, +} from 'service/api/mymeeting'; import { Paginated } from 'types/meeting'; -import { IMyMeetingManage } from 'types/myMeeting'; +import { + IMyMeetingLikes, + IMyMeetingManage, + IMyMeetingParticipated, +} from 'types/myMeeting'; export const myMeetingKeys = { all: ['mymeeting'] as const, manage: () => [...myMeetingKeys.all, 'manage'] as const, participated: () => [...myMeetingKeys.all, 'participated'] as const, + likes: () => [...myMeetingKeys.all, 'likes'] as const, memberProfile: (meetingId: number, userId: number) => [ ...myMeetingKeys.all, 'profile', @@ -18,6 +26,7 @@ export const myMeetingKeys = { ], }; +// 내가 생성한 모임 export const useInfiniteMyMeetingManageQueries = () => { return useInfiniteQuery({ queryKey: myMeetingKeys.manage(), @@ -29,12 +38,25 @@ export const useInfiniteMyMeetingManageQueries = () => { }); }; +// 내가 참여하고 있는 모임 export const useInfiniteMyMeetingParticipatedQueries = () => { return useInfiniteQuery({ queryKey: myMeetingKeys.participated(), queryFn: ({ pageParam }) => getMyMeetingParticipated(pageParam), initialPageParam: 0, - getNextPageParam: (lastPage: Paginated) => { + getNextPageParam: (lastPage: Paginated) => { + return lastPage.nextCursor ?? null; + }, + }); +}; + +// 내가 찜한 모임 +export const useInfiniteMyMeetingLikesQueries = () => { + return useInfiniteQuery({ + queryKey: myMeetingKeys.likes(), + queryFn: ({ pageParam }) => getMyMeetingLikes(pageParam), + initialPageParam: 0, + getNextPageParam: (lastPage: Paginated) => { return lastPage.nextCursor ?? null; }, }); diff --git a/src/service/api/meeting.ts b/src/service/api/meeting.ts index dc66106f..09aa6a80 100644 --- a/src/service/api/meeting.ts +++ b/src/service/api/meeting.ts @@ -7,6 +7,8 @@ import type { TopMeeting, } from 'types/meeting'; +import { likesURL, meetingURL, memberURL, myMeetingURL } from './endpoints'; + const getTopMeetings = async ( categoryTitle: CategoryTitle, ): Promise => { @@ -24,7 +26,7 @@ const getMeetings = async ( ): Promise> => { const newSearchQueryObj = { ...searchQueryObj, lastMeetingId: pageParams }; const res = await axiosInstance.post( - `/api/v1/meetings/search?categoryTitle=${category}`, + `${meetingURL.search}?categoryTitle=${category}`, newSearchQueryObj, ); @@ -32,13 +34,13 @@ const getMeetings = async ( }; const likeMeeting = async (meetingId: number) => { - const res = await axiosInstance.post(`/api/v1/meetings/${meetingId}/likes`); + const res = await axiosInstance.post(likesURL.create(meetingId)); return res.data.data; }; const cancelLikeMeeting = async (meetingId: number) => { - const res = await axiosInstance.delete(`/api/v1/meetings/${meetingId}/likes`); + const res = await axiosInstance.delete(likesURL.delete(meetingId)); return res.data.data; }; @@ -69,12 +71,12 @@ export interface MeetingManager { } const getMeetingDetail = async (id: number): Promise => { - const res = await axiosInstance.get(`/api/v1/meetings/detail/${id}`); + const res = await axiosInstance.get(meetingURL.detail(id)); return res.data.data; }; const getMeetingDetailManager = async (id: number): Promise => { - const res = await axiosInstance.get(`/api/v1/meetings/detail/manager/${id}`); + const res = await axiosInstance.get(meetingURL.managerDetail(id)); return res.data.data; }; @@ -86,7 +88,7 @@ const postMeetingRegister = async ({ meetingId: number; message: string; }) => { - const res = await axiosInstance.post(`/api/v1/members/${meetingId}`, { + const res = await axiosInstance.post(memberURL.create(meetingId), { message, }); return res.data.data; @@ -94,9 +96,7 @@ const postMeetingRegister = async ({ // Approve 상태에서 모임 탈퇴 const deleteMeetingQuit = async (meetingId: number) => { - const res = await axiosInstance.delete( - `/api/v1/mymeetings/quit/${meetingId}`, - ); + const res = await axiosInstance.delete(myMeetingURL.quit(meetingId)); return res.data.data; }; diff --git a/src/service/api/mymeeting.ts b/src/service/api/mymeeting.ts index f8995b63..c48b3ba3 100644 --- a/src/service/api/mymeeting.ts +++ b/src/service/api/mymeeting.ts @@ -1,6 +1,7 @@ import axiosInstance from '@/lib/axios/axiosInstance'; import { Paginated } from 'types/meeting'; import type { IMemberProfile, IMyMeetingManage } from 'types/myMeeting'; +import { IMyMeetingLikes, IMyMeetingParticipated } from 'types/myMeeting'; import { myMeetingURL } from './endpoints'; @@ -15,10 +16,10 @@ const getMyMeetingManage = async ( return res.data.data; }; -// 내가 참여하고있는 모임 불러오기기 +// 내가 참여하고있는 모임 불러오기 const getMyMeetingParticipated = async ( lastMeetingId: number, -): Promise> => { +): Promise> => { const res = await axiosInstance.get( `${myMeetingURL.all}?lastMeetingId=${lastMeetingId}&size=${6}`, ); @@ -26,6 +27,17 @@ const getMyMeetingParticipated = async ( return res.data.data; }; +// 내가 찜한 모임 불러오기 +const getMyMeetingLikes = async ( + lastMeetingId: number, +): Promise> => { + const res = await axiosInstance.get( + `${myMeetingURL.likes}?lastMeetingId=${lastMeetingId}&size=${6}`, + ); + + return res.data.data; +}; + // 맴버 프로필 불러오기 const getMyMeetingMemberProfile = async ({ userId, @@ -85,11 +97,26 @@ const putIsPublic = async (meetingId: number) => { return res.data.data; }; +// 참가중인 모임 나가기 +const deleteQuit = async (meetingId: number) => { + const res = await axiosInstance.delete(`${myMeetingURL.quit(meetingId)}`); + return res.data.data; +}; + +// 승인 대기중인 모임 취소하기 +const deleteCancel = async (meetingId: number) => { + const res = await axiosInstance.delete(`${myMeetingURL.cancel(meetingId)}`); + return res.data.data; +}; + export { getMyMeetingManage, getMyMeetingMemberProfile, getMyMeetingParticipated, + getMyMeetingLikes, putMemberStatus, putExpel, putIsPublic, + deleteQuit, + deleteCancel, }; diff --git a/src/types/myMeeting.ts b/src/types/myMeeting.ts index 8dece93c..07d5e7ec 100644 --- a/src/types/myMeeting.ts +++ b/src/types/myMeeting.ts @@ -1,3 +1,4 @@ +import { CategoryTitle } from './meeting'; import { IContactResponse } from './mypageTypes'; interface Member { @@ -30,6 +31,31 @@ interface IMyMeetingManage { memberList: Member[]; } +interface IMyMeetingParticipated { + categoryTitle: CategoryTitle; + meetingId: number; + title: string; + thumbnail: string; + location: string; + memberCount: number; + maxMember: number; + likesCount: number; + myMemberStatus: 'APPROVED' | 'REJECTED' | 'PENDING' | 'EXPEL'; + memberList: Member[]; + isMeetingManager: boolean; +} + +interface IMyMeetingLikes { + meetingId: number; + categoryTitle: CategoryTitle; + title: string; + thumbnail: string; + location: string; + memberCount: number; + maxMember: number; + likesCount: number; +} + interface IUserProfile { userId: number; name: string; @@ -75,4 +101,6 @@ export type { IMemberProfile, IBanner, UserData, + IMyMeetingParticipated, + IMyMeetingLikes, }; diff --git a/tailwind.config.ts b/tailwind.config.ts index 41406062..faf8f9d5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -10,6 +10,11 @@ export default { ], theme: { extend: { + animation: { + 'delay-100': 'bounce 1s infinite 100ms', + 'delay-200': 'bounce 1s infinite 200ms', + 'delay-300': 'bounce 1s infinite 300ms', + }, screens: { sm: { min: '375px',