From 3e904f02c5c971b6691e58ea4d75d340a69d344b Mon Sep 17 00:00:00 2001 From: yeonddori <126975394+yeonddori@users.noreply.github.com> Date: Sun, 26 Jan 2025 22:18:39 +0900 Subject: [PATCH] =?UTF-8?q?Feature/#380=20=ED=8C=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=ED=83=88=ED=87=B4=20(#381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 팀 탈퇴 api 정의 #380 * feat: 팀 삭제/탈퇴 모달 통합 #380 * feat: TeamControlPanel 리더 및 팀원 여부에 따른 버튼 표시 - 팀원 탈퇴 버튼 추가 #380 * feat: 스터디 탈퇴 모달 구현 #380 * feat: 스터디 탈퇴 구현 #380 * fix: router 경로 변경 - api 불러오는 이름 수정 #380 * refactor: 파일 경로 수정 #380 * refactor: 팀 삭제 모달과 탈퇴 모달 분리 #380 * fix: build error 수정 #380 * fix: 팀원이 탈퇴 버튼이 보이도록 수정 #380 * design: 탈퇴 버튼 색상 수정 #380 * fix: 스터디원이 아닌 팀원에게 탈퇴 버튼이 노출되는 문제 수정 #380 --- src/app/api/team.ts | 9 ++ src/app/team/[teamId]/page.tsx | 2 +- .../team/[teamId]/study/[studyId]/page.tsx | 15 ++- .../Sidebar/SidebarContent/index.tsx | 2 +- .../study/Modal/LeaveStudyModal/index.tsx | 43 +++++++ src/containers/study/Modal/types.ts | 6 + .../study/StudyControlPanel/index.tsx | 113 +++++++++++------- .../study/StudyControlPanel/types.ts | 3 + src/containers/team/DeleteTeamModal/type.ts | 6 - .../{ => Modal}/DeleteTeamModal/index.tsx | 2 +- .../team/Modal/LeaveTeamModal/index.tsx | 41 +++++++ .../team/{ => Modal}/TeamModal/index.tsx | 2 +- src/containers/team/Modal/type.ts | 17 +++ .../team/TeamControlPanel/index.tsx | 92 +++++++++----- src/containers/team/TeamControlPanel/types.ts | 2 + src/containers/team/TeamModal/type.ts | 7 -- 16 files changed, 269 insertions(+), 93 deletions(-) create mode 100644 src/containers/study/Modal/LeaveStudyModal/index.tsx delete mode 100644 src/containers/team/DeleteTeamModal/type.ts rename src/containers/team/{ => Modal}/DeleteTeamModal/index.tsx (95%) create mode 100644 src/containers/team/Modal/LeaveTeamModal/index.tsx rename src/containers/team/{ => Modal}/TeamModal/index.tsx (99%) create mode 100644 src/containers/team/Modal/type.ts delete mode 100644 src/containers/team/TeamModal/type.ts diff --git a/src/app/api/team.ts b/src/app/api/team.ts index 135c0811..712183de 100644 --- a/src/app/api/team.ts +++ b/src/app/api/team.ts @@ -101,6 +101,14 @@ const deleteTeamMember = (token: string, teamId: number, memberId: number) => }, }); +const leaveTeam = (token: string, teamId: number) => + teamFetcher(`/teams/${teamId}/members`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const mandateTeamLeader = (token: string, teamId: number, memberId: number) => teamFetcher(`/teams/${teamId}/mandate/${memberId}`, { method: 'PATCH', @@ -124,6 +132,7 @@ export { getTeams, getMyTeams, deleteTeamMember, + leaveTeam, mandateTeamLeader, getTeamMembers, }; diff --git a/src/app/team/[teamId]/page.tsx b/src/app/team/[teamId]/page.tsx index 5c1ef608..69f1ff3f 100644 --- a/src/app/team/[teamId]/page.tsx +++ b/src/app/team/[teamId]/page.tsx @@ -199,7 +199,7 @@ const Page = ({ params }: { params: { teamId: number } }) => { )} - {isTeamLeader && } + diff --git a/src/app/team/[teamId]/study/[studyId]/page.tsx b/src/app/team/[teamId]/study/[studyId]/page.tsx index 1e7a3b49..7115dac9 100644 --- a/src/app/team/[teamId]/study/[studyId]/page.tsx +++ b/src/app/team/[teamId]/study/[studyId]/page.tsx @@ -15,6 +15,7 @@ import CreateDocumentModal from '@/containers/study/CreateDocumentModal'; import { CreateDocument } from '@/containers/study/CreateDocumentModal/type'; import CurriculumCard from '@/containers/study/CurriculumCard'; import DeleteStudyModal from '@/containers/study/Modal/DeleteStudyModal'; +import LeaveStudyModal from '@/containers/study/Modal/LeaveStudyModal'; import StudyModal from '@/containers/study/Modal/StudyModal'; import TerminateStudyModal from '@/containers/study/Modal/TerminateStudyModal'; import Participant from '@/containers/study/Participant'; @@ -31,6 +32,7 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => { const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isTerminateModalOpen, setIsTerminateModalOpen] = useState(false); + const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false); const [documentArray, setDocumentArray] = useState([]); const [isCreateDocumentModalOpen, setIsCreateDocumentModalOpen] = useState(false); const categoryData: CreateDocument = { groupId: params.studyId, groupType: 'studies' }; @@ -95,11 +97,14 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => { )} - {studyData && studyData?.status !== 'ENDED' && user && user.memberId === studyData?.studyLeaderId && ( + {studyData && studyData?.status !== 'ENDED' && user && ( data.memberId === user.memberId)} editModalOpen={setIsEditModalOpen} terminateModalOpen={setIsTerminateModalOpen} deleteModalOpen={setIsDeleteModalOpen} + leaveModalOpen={setIsLeaveModalOpen} /> )} @@ -199,7 +204,13 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => { isOpen={isDeleteModalOpen} setIsOpen={setIsDeleteModalOpen} /> - + setIsCreateDocumentModalOpen(false)} diff --git a/src/components/Sidebar/SidebarContent/index.tsx b/src/components/Sidebar/SidebarContent/index.tsx index 6ff28eb4..1b8397f7 100644 --- a/src/components/Sidebar/SidebarContent/index.tsx +++ b/src/components/Sidebar/SidebarContent/index.tsx @@ -11,7 +11,7 @@ import { MdOutlineLogout } from 'react-icons/md'; import { useGetSideBarInfoQuery } from '@/app/api/member'; import { defaultUserAtom, myTeamAtom, userAtom } from '@/atom'; import GoogleLoginButton from '@/containers/main/GoogleLoginButton'; -import TeamModal from '@/containers/team/TeamModal'; +import TeamModal from '@/containers/team/Modal/TeamModal'; import useGetUser from '@/hooks/useGetUser'; import SidebarIconButton from '../Button/SidebarIconButton'; diff --git a/src/containers/study/Modal/LeaveStudyModal/index.tsx b/src/containers/study/Modal/LeaveStudyModal/index.tsx new file mode 100644 index 00000000..cbc8c466 --- /dev/null +++ b/src/containers/study/Modal/LeaveStudyModal/index.tsx @@ -0,0 +1,43 @@ +import { Text } from '@chakra-ui/react'; +import { useRouter } from 'next/navigation'; + +import { leaveStudy as leaveStudyApi } from '@/app/api/study'; +import ConfirmModal from '@/components/Modal/ConfirmModal'; +import { useMutateWithToken } from '@/hooks/useFetchWithToken'; +import useRefetchSideBar from '@/hooks/useRefetchSideBar'; + +import { LeaveStudyModalProps } from '../types'; + +const LeaveStudyModal = ({ id, name, teamId, isOpen, setIsOpen }: LeaveStudyModalProps) => { + const leaveStudy = useMutateWithToken(leaveStudyApi); + const refetchSidebar = useRefetchSideBar(); + const router = useRouter(); + + const handleClickLeave = () => { + leaveStudy(id).then((res) => { + if (res.ok) { + refetchSidebar(); + setIsOpen(false); + router.replace(`/team/${teamId}`); + } + }); + }; + + return ( + setIsOpen(false)} + title="스터디 탈퇴" + confirmButtonText="탈퇴" + onConfirmButtonClick={handleClickLeave} + > + + 스터디에서 탈퇴하면 다시 되돌릴 수 없습니다. +
+ {`"${name}"에서 탈퇴하시겠습니까?`} +
+
+ ); +}; + +export default LeaveStudyModal; diff --git a/src/containers/study/Modal/types.ts b/src/containers/study/Modal/types.ts index 26845b08..5fb2ed05 100644 --- a/src/containers/study/Modal/types.ts +++ b/src/containers/study/Modal/types.ts @@ -10,3 +10,9 @@ export interface DeleteStudyModalProps extends Pick { teamId: number; setIsOpen: React.Dispatch>; } + +export interface LeaveStudyModalProps extends Pick { + isOpen: boolean; + teamId: number; + setIsOpen: React.Dispatch>; +} diff --git a/src/containers/study/StudyControlPanel/index.tsx b/src/containers/study/StudyControlPanel/index.tsx index 259c7a89..84d270df 100644 --- a/src/containers/study/StudyControlPanel/index.tsx +++ b/src/containers/study/StudyControlPanel/index.tsx @@ -2,51 +2,78 @@ import { Button, Flex } from '@chakra-ui/react'; import { StudyControlPanelProps } from './types'; -const StudyControlPanel = ({ editModalOpen, terminateModalOpen, deleteModalOpen }: StudyControlPanelProps) => { +const StudyControlPanel = ({ + isStudyLeader, + isStudyMember, + editModalOpen, + terminateModalOpen, + deleteModalOpen, + leaveModalOpen, +}: StudyControlPanelProps) => { return ( - - - + {isStudyLeader && ( + <> + + + + + )} + {!isStudyLeader && isStudyMember && ( + + )} ); }; diff --git a/src/containers/study/StudyControlPanel/types.ts b/src/containers/study/StudyControlPanel/types.ts index d20a1714..f3b6a07f 100644 --- a/src/containers/study/StudyControlPanel/types.ts +++ b/src/containers/study/StudyControlPanel/types.ts @@ -1,5 +1,8 @@ export interface StudyControlPanelProps { + isStudyLeader: boolean; + isStudyMember: boolean; editModalOpen: React.Dispatch>; terminateModalOpen: React.Dispatch>; deleteModalOpen: React.Dispatch>; + leaveModalOpen: React.Dispatch>; } diff --git a/src/containers/team/DeleteTeamModal/type.ts b/src/containers/team/DeleteTeamModal/type.ts deleted file mode 100644 index 47cef8fa..00000000 --- a/src/containers/team/DeleteTeamModal/type.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Team } from '@/types'; - -export interface DeleteTeamModalProps extends Pick { - isOpen: boolean; - onClose: () => void; -} diff --git a/src/containers/team/DeleteTeamModal/index.tsx b/src/containers/team/Modal/DeleteTeamModal/index.tsx similarity index 95% rename from src/containers/team/DeleteTeamModal/index.tsx rename to src/containers/team/Modal/DeleteTeamModal/index.tsx index 046f3116..074e578d 100644 --- a/src/containers/team/DeleteTeamModal/index.tsx +++ b/src/containers/team/Modal/DeleteTeamModal/index.tsx @@ -6,7 +6,7 @@ import ConfirmModal from '@/components/Modal/ConfirmModal'; import { useMutateWithToken } from '@/hooks/useFetchWithToken'; import useRefetchSideBar from '@/hooks/useRefetchSideBar'; -import { DeleteTeamModalProps } from './type'; +import { DeleteTeamModalProps } from '../type'; const DeleteTeamModal = ({ id, name, isOpen, onClose }: DeleteTeamModalProps) => { const deleteTeam = useMutateWithToken(deleteTeamApi); diff --git a/src/containers/team/Modal/LeaveTeamModal/index.tsx b/src/containers/team/Modal/LeaveTeamModal/index.tsx new file mode 100644 index 00000000..aad5cc08 --- /dev/null +++ b/src/containers/team/Modal/LeaveTeamModal/index.tsx @@ -0,0 +1,41 @@ +import { Text } from '@chakra-ui/react'; +import { useRouter } from 'next/navigation'; + +import { leaveTeam as leaveTeamApi } from '@/app/api/team'; +import ConfirmModal from '@/components/Modal/ConfirmModal'; +import { useMutateWithToken } from '@/hooks/useFetchWithToken'; +import useRefetchSideBar from '@/hooks/useRefetchSideBar'; + +import { LeaveTeamModalProps } from '../type'; + +const LeaveTeamModal = ({ id, name, isOpen, onClose }: LeaveTeamModalProps) => { + const leaveTeam = useMutateWithToken(leaveTeamApi); + const refetchSidebar = useRefetchSideBar(); + const router = useRouter(); + + const handleLeaveTeamButtonClick = () => { + leaveTeam(id).then(() => { + refetchSidebar(); + onClose(); + router.replace('/'); + }); + }; + + return ( + handleLeaveTeamButtonClick()} + > + + 팀에서 탈퇴하면 다시 되돌릴 수 없습니다. +
+ {name} 팀에서 탈퇴하시겠습니까? +
+
+ ); +}; + +export default LeaveTeamModal; diff --git a/src/containers/team/TeamModal/index.tsx b/src/containers/team/Modal/TeamModal/index.tsx similarity index 99% rename from src/containers/team/TeamModal/index.tsx rename to src/containers/team/Modal/TeamModal/index.tsx index ff48a738..e7a03921 100644 --- a/src/containers/team/TeamModal/index.tsx +++ b/src/containers/team/Modal/TeamModal/index.tsx @@ -12,7 +12,7 @@ import { useMutateWithToken } from '@/hooks/useFetchWithToken'; import useRefetchSideBar from '@/hooks/useRefetchSideBar'; import useRefetchTeamInfo from '@/hooks/useRefetchTeamInfo'; -import { TeamModalProps } from './type'; +import { TeamModalProps } from '../type'; const AlertContent = ({ message }: { message: string }) => { return ( diff --git a/src/containers/team/Modal/type.ts b/src/containers/team/Modal/type.ts new file mode 100644 index 00000000..94dceb9a --- /dev/null +++ b/src/containers/team/Modal/type.ts @@ -0,0 +1,17 @@ +import { Team } from '@/types'; + +export interface TeamModalProps { + teamInfo?: Team; + isOpen: boolean; + onClose: () => void; +} + +export interface DeleteTeamModalProps extends Pick { + isOpen: boolean; + onClose: () => void; +} + +export interface LeaveTeamModalProps extends Pick { + isOpen: boolean; + onClose: () => void; +} diff --git a/src/containers/team/TeamControlPanel/index.tsx b/src/containers/team/TeamControlPanel/index.tsx index 8cdc4ca5..a48cbcdb 100644 --- a/src/containers/team/TeamControlPanel/index.tsx +++ b/src/containers/team/TeamControlPanel/index.tsx @@ -2,43 +2,65 @@ import { Button, Flex } from '@chakra-ui/react'; import { useState } from 'react'; import { TeamControlPanelProps } from './types'; -import DeleteTeamModal from '../DeleteTeamModal'; -import TeamModal from '../TeamModal'; +import DeleteTeamModal from '../Modal/DeleteTeamModal'; +import LeaveTeamModal from '../Modal/LeaveTeamModal'; +import TeamModal from '../Modal/TeamModal'; -const TeamControlPanel = ({ teamInfo }: TeamControlPanelProps) => { +const TeamControlPanel = ({ isTeamLeader, isMyTeam, teamInfo }: TeamControlPanelProps) => { const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false); return ( - - + {isTeamLeader && ( + <> + + + + )} + {!isTeamLeader && isMyTeam && ( + + )} {isEditModalOpen && ( setIsEditModalOpen(false)} /> )} @@ -50,6 +72,14 @@ const TeamControlPanel = ({ teamInfo }: TeamControlPanelProps) => { onClose={() => setIsDeleteModalOpen(false)} /> )} + {isLeaveModalOpen && ( + setIsLeaveModalOpen(false)} + /> + )} ); }; diff --git a/src/containers/team/TeamControlPanel/types.ts b/src/containers/team/TeamControlPanel/types.ts index 13f73659..2bcbdd85 100644 --- a/src/containers/team/TeamControlPanel/types.ts +++ b/src/containers/team/TeamControlPanel/types.ts @@ -1,5 +1,7 @@ import { TeamDetail } from '@/types'; export interface TeamControlPanelProps { + isTeamLeader: boolean; + isMyTeam: boolean; teamInfo: TeamDetail; } diff --git a/src/containers/team/TeamModal/type.ts b/src/containers/team/TeamModal/type.ts deleted file mode 100644 index 528229eb..00000000 --- a/src/containers/team/TeamModal/type.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Team } from '@/types'; - -export interface TeamModalProps { - teamInfo?: Team; - isOpen: boolean; - onClose: () => void; -}