diff --git a/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx b/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx index 723e892..0ee5998 100644 --- a/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx +++ b/src/app/(user-page)/my-meeting/_features/CardRightSection.tsx @@ -1,6 +1,5 @@ 'use client'; -import Dropdown from '@/components/common/Dropdown'; import { Button } from '@/components/ui/Button'; import { Tag } from '@/components/ui/Tag'; import Modal from '@/components/ui/modal/Modal'; @@ -8,12 +7,14 @@ import { useExpelMutation, useMemberStatusMutation, } from '@/hooks/mutations/useMyMeetingMutation'; +import { useBannerQueries } from '@/hooks/queries/useMyPageQueries'; import Image from 'next/image'; import { useState } from 'react'; import type { Member } from 'types/myMeeting'; import ModalProfile from './ModalProfile'; import ModalUserList from './ModalUserList'; +import PublicSelect from './PublicDropdown'; const CardRightSection = ({ memberList, @@ -26,17 +27,10 @@ const CardRightSection = ({ className?: string; meetingId: number; }) => { - 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); @@ -78,11 +72,36 @@ const CardRightSection = ({ setIsUserProfileModalOpen(false); }; + // 데스크탑 뷰에서 유저 프로필 보기 + const handleOpenProfileModal = ( + e: React.MouseEvent, + member: Member, + ) => { + e.stopPropagation(); + setSelectedUser(member); + setIsUserProfileModalOpen(true); + }; + // 프로필 보기 할 유저 const [selectedUser, setSelectedUser] = useState(null); + const { data: currentUser, isLoading, error } = useBannerQueries(); + if (isLoading || !currentUser) { + return; + } + return ( -
+
e.stopPropagation()} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.stopPropagation(); + } + }} + >

참가 중인 멤버

@@ -98,20 +117,23 @@ const CardRightSection = ({

{member.name}

-
- - -
+ {member.userId !== currentUser?.userId && ( +
+ + +
+ )}
))}
+ -
- -
+
diff --git a/src/app/(user-page)/my-meeting/_features/Created.tsx b/src/app/(user-page)/my-meeting/_features/Created.tsx index 5e80cf4..7a66a88 100644 --- a/src/app/(user-page)/my-meeting/_features/Created.tsx +++ b/src/app/(user-page)/my-meeting/_features/Created.tsx @@ -4,9 +4,9 @@ import HorizonCard from '@/components/ui/HorizonCard'; import useInfiniteScroll from '@/hooks/common/useInfiniteScroll'; import { useInfiniteMyMeetingManageQueries } from '@/hooks/queries/useMyMeetingQueries'; import { useRouter } from 'next/navigation'; -import { useState } from 'react'; import CardRightSection from './CardRightSection'; +import MeetingListSkeleton from './skeletons/SkeletonMeetingList'; const Created = () => { const router = useRouter(); @@ -27,7 +27,7 @@ const Created = () => { }); if (isLoading || !meetingData) { - return

loading...

; + return ; } const handleMoveDetailPage = (meetingId: number) => { @@ -42,111 +42,95 @@ const Created = () => {
{meetingData.pages.map((page, pageIdx) => (
- {page.content.map((meeting) => { - { - console.log( - 'page.nextCursor: ', - page.nextCursor, - 'meeting.meetingId: ', - meeting.meetingId, - ); - } - return ( -
- {/* 데스크탑 */} -
( +
+ {/* 데스크탑 */} +
+ - - - -
- - {/* 태블릿 */} -
- -
+ +
- {/* 모바일 */} -
- - -
+ {/* 태블릿 */} +
+ + +
+ + {/* 모바일 */} +
+ +
- ); - })} +
+ ))}
))}
diff --git a/src/app/(user-page)/my-meeting/_features/ModalProfile.tsx b/src/app/(user-page)/my-meeting/_features/ModalProfile.tsx index 31d5d6e..56cde52 100644 --- a/src/app/(user-page)/my-meeting/_features/ModalProfile.tsx +++ b/src/app/(user-page)/my-meeting/_features/ModalProfile.tsx @@ -3,6 +3,8 @@ import { useMyMeetingMemberProfileQuries } from '@/hooks/queries/useMyMeetingQue import Image from 'next/image'; import React from 'react'; +import SkeletonProfile from './skeletons/SkeletonProfile'; + const ModalProfile = ({ userId, meetingId, @@ -20,7 +22,7 @@ const ModalProfile = ({ }); if (isLoading || !user) { - return
로딩중
; + return ; } return ( diff --git a/src/app/(user-page)/my-meeting/_features/ModalUserList.tsx b/src/app/(user-page)/my-meeting/_features/ModalUserList.tsx index c09fbc1..d2890a9 100644 --- a/src/app/(user-page)/my-meeting/_features/ModalUserList.tsx +++ b/src/app/(user-page)/my-meeting/_features/ModalUserList.tsx @@ -3,7 +3,7 @@ import { useBannerQueries } from '@/hooks/queries/useMyPageQueries'; import Image from 'next/image'; import React from 'react'; import { Dispatch, SetStateAction, useState } from 'react'; -import type { Member } from 'types/myMeeting'; +import type { IBanner, Member } from 'types/myMeeting'; import { Button } from '../../../../components/ui/Button'; @@ -12,14 +12,16 @@ const ModalUserList = ({ setIsUserProfileModalOpen, setIsUserListModalOpen, setSelectedUser, + currentUser, + className, }: { memberList: Member[]; setSelectedUser: Dispatch>; setIsUserProfileModalOpen: Dispatch>; setIsUserListModalOpen: Dispatch>; + currentUser: IBanner; + className?: string; }) => { - const { data: currentUser, isLoading, error } = useBannerQueries(); - const handleProfileClick = (user: Member) => { setSelectedUser(user); setIsUserProfileModalOpen(true); @@ -28,38 +30,40 @@ const ModalUserList = ({ return (
-

맴버 리스트

- {memberList.map((user) => ( -
-
- 유저 프로필 -

{user.name}

-
- {user.userId !== currentUser?.userId && ( -
- -
- -
+

맴버 리스트

+
+ {memberList.map((user) => ( +
+
+ 유저 프로필 +

{user.name}

- )} -
- ))} + {user.userId !== currentUser?.userId && ( +
+ +
+ +
+
+ )} +
+ ))} +
); }; diff --git a/src/app/(user-page)/my-meeting/_features/PublicDropdown.tsx b/src/app/(user-page)/my-meeting/_features/PublicDropdown.tsx new file mode 100644 index 0000000..c4bfcf6 --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/PublicDropdown.tsx @@ -0,0 +1,32 @@ +'use client'; + +import Dropdown from '@/components/common/Dropdown'; +import { useChangePublic } from '@/hooks/mutations/useMyMeetingMutation'; + +const PublicDropdown = ({ + isPublic, + meetingId, +}: { + isPublic: boolean; + meetingId: number; +}) => { + const { mutate } = useChangePublic(meetingId); + + const filterAreaOptions = [ + { value: 'true', label: '공개', onSelect: () => mutate(true) }, + { value: 'false', label: '비공개', onSelect: () => mutate(false) }, + ]; + + return ( +
+ +
+ ); +}; +export default PublicDropdown; diff --git a/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonMeetingList.tsx b/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonMeetingList.tsx new file mode 100644 index 0000000..8f77cd5 --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonMeetingList.tsx @@ -0,0 +1,18 @@ +import SkeletonBasic from '@/components/ui/SkeletonBasic'; + +const MeetingListSkeleton = () => { + const skeletonCount = [1, 2, 3]; + + return ( +
+ {skeletonCount.map((_, idx) => ( +
+ +
+
+ ))} +
+ ); +}; + +export default MeetingListSkeleton; diff --git a/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonProfile.tsx b/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonProfile.tsx new file mode 100644 index 0000000..9aea8b0 --- /dev/null +++ b/src/app/(user-page)/my-meeting/_features/skeletons/SkeletonProfile.tsx @@ -0,0 +1,59 @@ +import Description from '@/components/common/Description'; +import SkeletonBasic from '@/components/ui/SkeletonBasic'; + +const SkeletonProfile = () => { + return ( +
+
+ + +
+

{}

+

{}

+
+
+
+
+ + + + + + + + + + + + +
+
+ + + + + + +
+
+ + + + + + + + + +
+
+ + + +
+
+
+ ); +}; + +export default SkeletonProfile; diff --git a/src/components/common/Dropdown.tsx b/src/components/common/Dropdown.tsx index 022fbc7..ad94081 100644 --- a/src/components/common/Dropdown.tsx +++ b/src/components/common/Dropdown.tsx @@ -164,14 +164,20 @@ const Dropdown = ({ {options.map((option) => ( handleSelect(option)} + onSelect={ + option.value === selectedValue + ? undefined + : () => handleSelect(option) + } className={cn( 'relative flex w-full cursor-pointer select-none items-center', 'typo-body1 h-[34px] rounded-[10px] px-[12px] py-[8px]', - 'text-Cgray400 hover:bg-Cgray300 hover:text-Cgray700', + 'text-Cgray400', 'outline-none transition-colors', size === 's' ? 'typo-body2' : 'typo-body1', - option.value === selectedValue && 'bg-Cgray300 text-Cgray700', + option.value === selectedValue + ? 'cursor-default text-Cgray300' + : 'hover:bg-Cgray300 hover:text-Cgray700', )} > {option.label} diff --git a/src/hooks/mutations/useMyMeetingMutation.ts b/src/hooks/mutations/useMyMeetingMutation.ts index 7e262ac..64c69cb 100644 --- a/src/hooks/mutations/useMyMeetingMutation.ts +++ b/src/hooks/mutations/useMyMeetingMutation.ts @@ -1,7 +1,7 @@ import { useToast } from '@/components/common/ToastContext'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; -import { putExpel, putMemberStatus } from 'service/api/mymeeting'; +import { putExpel, putIsPublic, putMemberStatus } from 'service/api/mymeeting'; import { myMeetingKeys } from '../queries/useMyMeetingQueries'; @@ -69,4 +69,30 @@ const useExpelMutation = (meetingId: number) => { }); }; -export { useMemberStatusMutation, useExpelMutation }; +// 공개 / 비공개 변경 +const useChangePublic = (meetingId: number) => { + const { showToast } = useToast(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (isPublic: boolean) => putIsPublic(meetingId), + onSuccess: (data, variables) => { + console.log('비공개 api 응답 확인 data: ', data); + if (!data.isPublic) { + showToast('해당 모임을 비공개로 바꿨어요!', 'success'); + } else { + showToast('해당 모임을 공개로 바꿨어요!', 'success'); + } + queryClient.invalidateQueries({ + queryKey: myMeetingKeys.manage(), + }); + }, + onError: (error: AxiosError) => { + if (error.response?.status) { + showToast('다시 시도해주세요.', 'error'); + } + }, + }); +}; + +export { useMemberStatusMutation, useExpelMutation, useChangePublic }; diff --git a/src/service/api/mymeeting.ts b/src/service/api/mymeeting.ts index 9fc608e..1a07313 100644 --- a/src/service/api/mymeeting.ts +++ b/src/service/api/mymeeting.ts @@ -65,9 +65,17 @@ const putExpel = async ({ return res.data.data; }; + +// 공개 / 비공개 설정 +const putIsPublic = async (meetingId: number) => { + const res = await authAPI.put(`/api/v1/mymeetings/isPublic/${meetingId}`); + return res.data.data; +}; + export { getMyMeetingManage, getMyMeetingMemberProfile, putMemberStatus, putExpel, + putIsPublic, };