Skip to content

Commit

Permalink
Feature/#380 팀 및 스터디 탈퇴 (#381)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yeonddori authored Jan 26, 2025
1 parent 924bc2f commit 3e904f0
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 93 deletions.
9 changes: 9 additions & 0 deletions src/app/api/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -124,6 +132,7 @@ export {
getTeams,
getMyTeams,
deleteTeamMember,
leaveTeam,
mandateTeamLeader,
getTeamMembers,
};
2 changes: 1 addition & 1 deletion src/app/team/[teamId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ const Page = ({ params }: { params: { teamId: number } }) => {
</Flex>
)}
</Flex>
{isTeamLeader && <TeamControlPanel teamInfo={teamInfo?.body} />}
<TeamControlPanel isTeamLeader={isTeamLeader} teamInfo={teamInfo?.body} isMyTeam={isMyTeam} />

<Flex pos="relative" align="center" flex="1" gap="8">
<Box pos="relative" overflow="hidden" w="100%" h={{ base: '250px', md: '300px', xl: '320px' }}>
Expand Down
15 changes: 13 additions & 2 deletions src/app/team/[teamId]/study/[studyId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,6 +32,7 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => {
const [isEditModalOpen, setIsEditModalOpen] = useState<boolean>(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
const [isTerminateModalOpen, setIsTerminateModalOpen] = useState<boolean>(false);
const [isLeaveModalOpen, setIsLeaveModalOpen] = useState<boolean>(false);
const [documentArray, setDocumentArray] = useState<DocumentList[]>([]);
const [isCreateDocumentModalOpen, setIsCreateDocumentModalOpen] = useState<boolean>(false);
const categoryData: CreateDocument = { groupId: params.studyId, groupType: 'studies' };
Expand Down Expand Up @@ -95,11 +97,14 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => {
</>
)}
</Flex>
{studyData && studyData?.status !== 'ENDED' && user && user.memberId === studyData?.studyLeaderId && (
{studyData && studyData?.status !== 'ENDED' && user && (
<StudyControlPanel
isStudyLeader={user.memberId === studyData.studyLeaderId}
isStudyMember={result?.some((data: { memberId: number }) => data.memberId === user.memberId)}
editModalOpen={setIsEditModalOpen}
terminateModalOpen={setIsTerminateModalOpen}
deleteModalOpen={setIsDeleteModalOpen}
leaveModalOpen={setIsLeaveModalOpen}
/>
)}
<Grid gap="4" templateColumns={{ base: '', xl: '2fr 1fr' }} w="100%" my="4">
Expand Down Expand Up @@ -199,7 +204,13 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => {
isOpen={isDeleteModalOpen}
setIsOpen={setIsDeleteModalOpen}
/>

<LeaveStudyModal
id={params.studyId}
name={studyData?.name || ''}
teamId={params.teamId}
isOpen={isLeaveModalOpen}
setIsOpen={setIsLeaveModalOpen}
/>
<CreateDocumentModal
isOpen={isCreateDocumentModalOpen}
onClose={() => setIsCreateDocumentModalOpen(false)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Sidebar/SidebarContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
43 changes: 43 additions & 0 deletions src/containers/study/Modal/LeaveStudyModal/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ConfirmModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="스터디 탈퇴"
confirmButtonText="탈퇴"
onConfirmButtonClick={handleClickLeave}
>
<Text align="center">
스터디에서 탈퇴하면 다시 되돌릴 수 없습니다.
<br />
{`"${name}"에서 탈퇴하시겠습니까?`}
</Text>
</ConfirmModal>
);
};

export default LeaveStudyModal;
6 changes: 6 additions & 0 deletions src/containers/study/Modal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ export interface DeleteStudyModalProps extends Pick<Study, 'id' | 'name'> {
teamId: number;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

export interface LeaveStudyModalProps extends Pick<Study, 'id' | 'name'> {
isOpen: boolean;
teamId: number;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
113 changes: 70 additions & 43 deletions src/containers/study/StudyControlPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Flex gap="2">
<Button
w="fit-content"
px="4"
py="1"
color="white"
bg="orange"
shadow="md"
_hover={{ bg: 'orange' }}
aria-label=""
onClick={() => editModalOpen(true)}
size="xs"
>
수정
</Button>
<Button
w="fit-content"
px="4"
py="1"
color="white"
bg="orange_dark"
shadow="md"
_hover={{ bg: 'orange_dark' }}
aria-label=""
onClick={() => terminateModalOpen(true)}
size="xs"
>
종료
</Button>
<Button
w="fit-content"
px="4"
py="1"
color="black"
bg="white"
shadow="md"
_hover={{ bg: 'white' }}
aria-label=""
onClick={() => deleteModalOpen(true)}
size="xs"
>
삭제
</Button>
{isStudyLeader && (
<>
<Button
w="fit-content"
px="4"
py="1"
color="white"
bg="orange"
shadow="md"
_hover={{ bg: 'orange' }}
aria-label=""
onClick={() => editModalOpen(true)}
size="xs"
>
수정
</Button>
<Button
w="fit-content"
px="4"
py="1"
color="white"
bg="orange_dark"
shadow="md"
_hover={{ bg: 'orange_dark' }}
aria-label=""
onClick={() => terminateModalOpen(true)}
size="xs"
>
종료
</Button>
<Button
w="fit-content"
px="4"
py="1"
color="black"
bg="white"
shadow="md"
_hover={{ bg: 'white' }}
aria-label=""
onClick={() => deleteModalOpen(true)}
size="xs"
>
삭제
</Button>
</>
)}
{!isStudyLeader && isStudyMember && (
<Button
w="fit-content"
px="4"
py="1"
color="white"
bg="orange"
shadow="md"
_hover={{ bg: 'orange' }}
aria-label=""
onClick={() => leaveModalOpen(true)}
size="xs"
>
탈퇴
</Button>
)}
</Flex>
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/containers/study/StudyControlPanel/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export interface StudyControlPanelProps {
isStudyLeader: boolean;
isStudyMember: boolean;
editModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
terminateModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
deleteModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
leaveModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
6 changes: 0 additions & 6 deletions src/containers/team/DeleteTeamModal/type.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
41 changes: 41 additions & 0 deletions src/containers/team/Modal/LeaveTeamModal/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ConfirmModal
isOpen={isOpen}
onClose={onClose}
title="팀 탈퇴"
confirmButtonText="탈퇴"
onConfirmButtonClick={() => handleLeaveTeamButtonClick()}
>
<Text align="center">
팀에서 탈퇴하면 다시 되돌릴 수 없습니다.
<br />
{name} 팀에서 탈퇴하시겠습니까?
</Text>
</ConfirmModal>
);
};

export default LeaveTeamModal;
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
17 changes: 17 additions & 0 deletions src/containers/team/Modal/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Team } from '@/types';

export interface TeamModalProps {
teamInfo?: Team;
isOpen: boolean;
onClose: () => void;
}

export interface DeleteTeamModalProps extends Pick<Team, 'id' | 'name'> {
isOpen: boolean;
onClose: () => void;
}

export interface LeaveTeamModalProps extends Pick<Team, 'id' | 'name'> {
isOpen: boolean;
onClose: () => void;
}
Loading

0 comments on commit 3e904f0

Please sign in to comment.