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; -}