Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#380 팀 및 스터디 탈퇴 #381

Merged
merged 13 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/app/api/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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 @@ -115,6 +123,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
12 changes: 11 additions & 1 deletion 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 @@ -97,9 +99,11 @@ const Page = ({ params }: { params: { teamId: number; studyId: number } }) => {
</Flex>
{studyData && studyData?.status !== 'ENDED' && user && user.memberId === studyData?.studyLeaderId && (
<StudyControlPanel
isStudyLeader={user.memberId === studyData.studyLeaderId}
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 +203,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>>;
}
112 changes: 69 additions & 43 deletions src/containers/study/StudyControlPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,77 @@ import { Button, Flex } from '@chakra-ui/react';

import { StudyControlPanelProps } from './types';

const StudyControlPanel = ({ editModalOpen, terminateModalOpen, deleteModalOpen }: StudyControlPanelProps) => {
const StudyControlPanel = ({
isStudyLeader,
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 && (
<Button
w="fit-content"
px="4"
py="1"
color="black"
bg="white"
shadow="md"
_hover={{ bg: 'white' }}
aria-label=""
onClick={() => leaveModalOpen(true)}
size="xs"
>
탈퇴
</Button>
)}
</Flex>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/containers/study/StudyControlPanel/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export interface StudyControlPanelProps {
isStudyLeader: 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