diff --git a/src/RoomSetting/tabs/MemberTab.tsx b/src/RoomSetting/tabs/MemberTab.tsx
deleted file mode 100644
index 6c27be28..00000000
--- a/src/RoomSetting/tabs/MemberTab.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-const MemberTab = () => {
- return <>Member 탭입니다>;
-};
-
-export default MemberTab;
diff --git a/src/RoomSetting/tabs/MemberTab/DelegationButton.tsx b/src/RoomSetting/tabs/MemberTab/DelegationButton.tsx
new file mode 100644
index 00000000..ace28883
--- /dev/null
+++ b/src/RoomSetting/tabs/MemberTab/DelegationButton.tsx
@@ -0,0 +1,77 @@
+import { useMutation } from '@tanstack/react-query';
+import roomAPI from '@/core/api/functions/roomAPI';
+import { useMoveRoute } from '@/core/hooks';
+import { ModalHeadingStyle, descriptionStyle, errorStyle } from './styles';
+import { BottomSheet, useBottomSheet } from '@/shared/BottomSheet';
+import { LoadingSpinner } from '@/shared/LoadingSpinner';
+
+interface DelegationButtonProps {
+ roomId: string;
+ memberId: string;
+ nickname: string;
+}
+
+const DelegationButton = ({
+ roomId,
+ memberId,
+ nickname
+}: DelegationButtonProps) => {
+ const { bottomSheetProps, open } = useBottomSheet();
+
+ const { mutate, isPending, error } = useMutation({
+ mutationFn: roomAPI.putDelegateMaster
+ });
+
+ const moveTo = useMoveRoute();
+
+ const handleDelegation = () => {
+ mutate(
+ { roomId, memberId },
+ {
+ onSuccess: () => {
+ moveTo('roomDetail');
+ // TODO: 토스트 메시지로 방장을 위임했음을 알려야 해요.
+ },
+ onError: (err) => console.error(err)
+ }
+ );
+ };
+
+ return (
+ <>
+
+
+
+ "{nickname}" 님에게
+
+
+ 방장을 위임
+
+ 하시겠어요?
+
+
+
+ 위임 후, 관리 페이지에서 자동으로 나가집니다.
+
+ {error && {error.response?.data.message}
}
+
+
+ >
+ );
+};
+
+export default DelegationButton;
diff --git a/src/RoomSetting/tabs/MemberTab/KickButton.tsx b/src/RoomSetting/tabs/MemberTab/KickButton.tsx
new file mode 100644
index 00000000..b4cfe646
--- /dev/null
+++ b/src/RoomSetting/tabs/MemberTab/KickButton.tsx
@@ -0,0 +1,88 @@
+import { useState } from 'react';
+import { useMutation } from '@tanstack/react-query';
+import roomAPI from '@/core/api/functions/roomAPI';
+import { ModalHeadingStyle, descriptionStyle } from './styles';
+import { Input } from '@/shared/Input';
+import { BottomSheet, useBottomSheet } from '@/shared/BottomSheet';
+import { LoadingSpinner } from '@/shared/LoadingSpinner';
+
+interface KickButtonProps {
+ roomId: string;
+ memberId: string;
+ nickname: string;
+}
+
+const KickButton = ({ roomId, memberId, nickname }: KickButtonProps) => {
+ const [confirmInput, setConfirmInput] = useState('');
+
+ const { bottomSheetProps, open, close } = useBottomSheet();
+
+ const { mutate, isPending, error } = useMutation({
+ mutationFn: roomAPI.deleteKickUser
+ });
+
+ const handleKick = () => {
+ mutate(
+ { roomId, memberId },
+ {
+ onSuccess: () => {
+ close();
+ // TODO: 토스트 메시지로 사용자를 추방했음을 알려야 해요.
+ },
+ onError: (err) => console.error(err)
+ }
+ );
+ };
+
+ return (
+ <>
+
+
+
+ "{nickname}" 님을
+
+ 추방하시겠어요?
+
+
+ 바쁜 일이 있었던 걸지도 몰라요.
+
+ {error && (
+
+ {error.response?.data.message}
+
+ )}
+
+
+ >
+ );
+};
+
+export default KickButton;
diff --git a/src/RoomSetting/tabs/MemberTab/index.tsx b/src/RoomSetting/tabs/MemberTab/index.tsx
new file mode 100644
index 00000000..dff17a79
--- /dev/null
+++ b/src/RoomSetting/tabs/MemberTab/index.tsx
@@ -0,0 +1,46 @@
+import { useSuspenseQuery } from '@tanstack/react-query';
+import { roomOptions } from '@/core/api/options';
+import KickButton from './KickButton';
+import DelegationButton from './DelegationButton';
+import { Avatar } from '@/shared/Avatar';
+
+interface MemberTabProps {
+ roomId: string;
+}
+
+const MemberTab = ({ roomId }: MemberTabProps) => {
+ const { data: room } = useSuspenseQuery({
+ ...roomOptions.detail(roomId),
+ staleTime: Infinity
+ });
+
+ return (
+
+ {room.todayCertificateRank.map((member) => (
+
+ ))}
+
+ );
+};
+
+export default MemberTab;
diff --git a/src/RoomSetting/tabs/MemberTab/styles.ts b/src/RoomSetting/tabs/MemberTab/styles.ts
new file mode 100644
index 00000000..88583617
--- /dev/null
+++ b/src/RoomSetting/tabs/MemberTab/styles.ts
@@ -0,0 +1,8 @@
+// 모달의 헤딩에 적용되는 스타일
+export const ModalHeadingStyle = 'flex flex-col text-xl mt-4 leading-7';
+
+// 회색으로 간결한 설명을 적을 때 적용할 스타일
+export const descriptionStyle = 'mt-2 text-base text-dark-gray';
+
+// 빨간색으로 에러를 표시할 때 적용할 스타일
+export const errorStyle = 'mt-2 text-base text-danger';
diff --git a/src/RoomSetting/tabs/RemoveTab.tsx b/src/RoomSetting/tabs/RemoveTab.tsx
index 5c71ff78..0a6dc4f3 100644
--- a/src/RoomSetting/tabs/RemoveTab.tsx
+++ b/src/RoomSetting/tabs/RemoveTab.tsx
@@ -1,5 +1,87 @@
-const RemoveTab = () => {
- return <>Remove 탭입니다>;
+import { useState } from 'react';
+import { useMutation, useSuspenseQuery } from '@tanstack/react-query';
+import { roomOptions } from '@/core/api/options';
+import roomAPI from '@/core/api/functions/roomAPI';
+import { useMoveRoute } from '@/core/hooks';
+import { Input } from '@/shared/Input';
+import { LoadingSpinner } from '@/shared/LoadingSpinner';
+
+interface RemoveTabProps {
+ roomId: string;
+}
+
+const RemoveTab = ({ roomId }: RemoveTabProps) => {
+ const [confirmInput, setConfirmInput] = useState('');
+ const moveTo = useMoveRoute();
+
+ const { data: room } = useSuspenseQuery({
+ ...roomOptions.detail(roomId),
+ staleTime: Infinity
+ });
+
+ const { mutate, isPending, error } = useMutation({
+ mutationFn: roomAPI.deleteRoom
+ });
+
+ const handleRemove = () => {
+ mutate(roomId, {
+ onSuccess: () => {
+ moveTo('routines');
+ // TODO: 토스트 메시지로 방을 삭제했음을 알려야 해요.
+ },
+ onError: (err) => console.error(err)
+ });
+ };
+
+ if (room.currentUserCount > 1) {
+ return (
+ <>
+
+
방에 혼자 남았을 때만
+ 삭제할 수 있어요.
+
+ >
+ );
+ } else {
+ return (
+ <>
+ 방을 삭제할까요?
+
+ {error && (
+
+ {error.response?.data.message}
+
+ )}
+
+ >
+ );
+ }
};
export default RemoveTab;
+
+// 헤딩에 적용할 스타일
+const headingStyle = 'text-xl font-bold';
+
+// 회색으로 간결한 설명을 적을 때 적용할 스타일
+const descriptionStyle = 'text-base text-dark-gray';
diff --git a/src/core/api/functions/roomAPI.ts b/src/core/api/functions/roomAPI.ts
index cad2b518..c1b3e9cb 100644
--- a/src/core/api/functions/roomAPI.ts
+++ b/src/core/api/functions/roomAPI.ts
@@ -12,6 +12,11 @@ const roomAPI = {
}) => {
return await baseInstance.post<{ message: string }>('/rooms', body);
},
+
+ getRoomDetail: async (roomId: string) => {
+ return await baseInstance.get(`/rooms/${roomId}`);
+ },
+
putRoom: async (params: {
roomId: string;
title: string;
@@ -24,8 +29,21 @@ const roomAPI = {
const { roomId, ...body } = params;
return await baseInstance.put(`/rooms/${roomId}`, body);
},
- getRoomDetail: async (roomId: string) => {
- return await baseInstance.get(`/rooms/${roomId}`);
+
+ deleteRoom: async (roomId: string) => {
+ return await baseInstance.delete(`/rooms/${roomId}`);
+ },
+
+ deleteKickUser: async (params: { roomId: string; memberId: string }) => {
+ const { roomId, memberId } = params;
+ return await baseInstance.delete(`/rooms/${roomId}/members/${memberId}`);
+ },
+
+ putDelegateMaster: async (params: { roomId: string; memberId: string }) => {
+ const { roomId, memberId } = params;
+ return await baseInstance.put(
+ `/rooms/${roomId}/members/${memberId}/delegation`
+ );
}
};
diff --git a/src/core/mocks/handlers/rooms.ts b/src/core/mocks/handlers/rooms.ts
index a541e723..9425ac69 100644
--- a/src/core/mocks/handlers/rooms.ts
+++ b/src/core/mocks/handlers/rooms.ts
@@ -34,6 +34,27 @@ const roomsHandlers = [
return HttpResponse.json(response, { status });
}),
+ http.get(baseURL('/rooms/:roomId'), async () => {
+ await delay(1000);
+
+ const status: number = 200;
+ let response = {};
+
+ switch (status) {
+ case 200:
+ response = RoomInfo;
+ break;
+ case 401:
+ response = { message: '존재하지 않는 유저입니다.' };
+ break;
+ case 404:
+ response = { message: '존재하지 않는 방입니다.' };
+ break;
+ }
+
+ return HttpResponse.json(response, { status });
+ }),
+
http.put(baseURL('/rooms/:roomId'), async () => {
await delay(1000);
@@ -68,7 +89,7 @@ const roomsHandlers = [
return HttpResponse.json(response, { status });
}),
- http.get(baseURL('/rooms/:roomId'), async () => {
+ http.delete(baseURL('/rooms/:roomId'), async () => {
await delay(1000);
const status: number = 200;
@@ -76,13 +97,57 @@ const roomsHandlers = [
switch (status) {
case 200:
- response = RoomInfo;
+ response = {};
+ break;
+ case 400:
+ response = {
+ message: '방장은 나가려면 위임 하거나 혼자 남아있어야 합니다.'
+ };
break;
case 401:
response = { message: '존재하지 않는 유저입니다.' };
break;
- case 404:
- response = { message: '존재하지 않는 방입니다.' };
+ }
+
+ return HttpResponse.json(response, { status });
+ }),
+
+ http.delete(baseURL('/rooms/:roomId/members/:memberId'), async () => {
+ await delay(1000);
+
+ const status: number = 200;
+ let response = {};
+
+ switch (status) {
+ case 200:
+ response = {};
+ break;
+ case 400:
+ response = { message: '방장만 추방할 수 있습니다.' };
+ break;
+ case 401:
+ response = { message: '존재하지 않는 유저입니다.' };
+ break;
+ }
+
+ return HttpResponse.json(response, { status });
+ }),
+
+ http.put(baseURL('/rooms/:roomId/members/:memberId/delegation'), async () => {
+ await delay(1000);
+
+ const status: number = 200;
+ let response = {};
+
+ switch (status) {
+ case 200:
+ response = {};
+ break;
+ case 400:
+ response = { message: '방장만 위임할 수 있습니다.' };
+ break;
+ case 401:
+ response = { message: '존재하지 않는 유저입니다.' };
break;
}
diff --git a/src/pages/RoomSettingPage.tsx b/src/pages/RoomSettingPage.tsx
index ddaa4234..49002ea5 100644
--- a/src/pages/RoomSettingPage.tsx
+++ b/src/pages/RoomSettingPage.tsx
@@ -24,10 +24,14 @@ const RoomSettingPage = () => {
-
+ }>
+
+
-
+ }>
+
+