-
Notifications
You must be signed in to change notification settings - Fork 1
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
feat: 방 관리 기능 완성 #137
Merged
Merged
feat: 방 관리 기능 완성 #137
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
5ee43ba
feat: 멤버 관리 탭 화면 및 바텀시트 UI 작업
bbearcookie 618da5a
feat: 방장 위임 바텀시트 UI 작업
bbearcookie 3169b20
feat: 바텀시트에 실제 유저 닉네임 적용
bbearcookie 24901da
feat: 방 삭제 탭 UI 작업
bbearcookie 3c0aac6
feat: 방 삭제, 방장 위임, 멤버 추방 API 함수 및 모킹 핸들러 작성
bbearcookie 3ceaac5
feat: 방장 위임 API 호출 완성
bbearcookie 45edead
feat: 방 삭제 API 호출 완성
bbearcookie 1c4f889
feat: 추방 API 호출 완성
bbearcookie a5a0c14
Merge branch 'main' into feat/#133/room-member-manage
bbearcookie 84a5fed
Merge branch 'main' into feat/#133/room-member-manage
bbearcookie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<> | ||
<button | ||
className="btn btn-light-point dark:btn-dark-point rounded-2xl px-3 py-1" | ||
onClick={open} | ||
> | ||
방장 위임 | ||
</button> | ||
<BottomSheet | ||
{...bottomSheetProps} | ||
className="p-6" | ||
> | ||
<h1 className={ModalHeadingStyle}> | ||
<b>"{nickname}" 님에게</b> | ||
<b> | ||
<span className="font-bold text-light-point dark:text-dark-point"> | ||
방장을 위임 | ||
</span> | ||
하시겠어요? | ||
</b> | ||
</h1> | ||
<p className={descriptionStyle}> | ||
위임 후, 관리 페이지에서 자동으로 나가집니다. | ||
</p> | ||
{error && <p className={errorStyle}>{error.response?.data.message}</p>} | ||
<button | ||
className="btn btn-light-point dark:btn-dark-point mt-6 flex w-full items-center justify-center" | ||
onClick={handleDelegation} | ||
disabled={isPending} | ||
> | ||
{isPending ? <LoadingSpinner size="2xl" /> : '방장 위임'} | ||
</button> | ||
</BottomSheet> | ||
</> | ||
); | ||
}; | ||
|
||
export default DelegationButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<> | ||
<button | ||
className="btn btn-danger rounded-2xl px-3 py-1" | ||
onClick={open} | ||
> | ||
추방 | ||
</button> | ||
<BottomSheet | ||
{...bottomSheetProps} | ||
className="p-6" | ||
> | ||
<h1 className={ModalHeadingStyle}> | ||
<b>"{nickname}" 님을</b> | ||
<b> | ||
<span className="font-bold text-danger">추방</span>하시겠어요? | ||
</b> | ||
</h1> | ||
<p className={descriptionStyle}>바쁜 일이 있었던 걸지도 몰라요.</p> | ||
<section className="mt-10 flex flex-col gap-2"> | ||
<label | ||
htmlFor="nickname" | ||
className="text-base" | ||
> | ||
추방하려는 멤버의 닉네임을 적어주세요. | ||
</label> | ||
<Input | ||
id="nickname" | ||
value={confirmInput} | ||
autoComplete="off" | ||
placeholder={nickname} | ||
onChange={(e) => setConfirmInput(e.target.value)} | ||
/> | ||
</section> | ||
{error && ( | ||
<p className="ml-2 mt-2 text-base text-danger"> | ||
{error.response?.data.message} | ||
</p> | ||
)} | ||
<button | ||
className="btn btn-danger mt-6 flex w-full items-start justify-center" | ||
onClick={handleKick} | ||
disabled={confirmInput !== nickname || isPending} | ||
> | ||
{isPending ? <LoadingSpinner size="2xl" /> : '추방'} | ||
</button> | ||
</BottomSheet> | ||
</> | ||
); | ||
}; | ||
|
||
export default KickButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="flex flex-col gap-4"> | ||
{room.todayCertificateRank.map((member) => ( | ||
<div | ||
className="flex items-center justify-between" | ||
key={member.memberId} | ||
> | ||
<Avatar | ||
imgUrl={member.profileImage} | ||
nickname={member.nickname} | ||
userId={member.memberId} | ||
contribution={member.contributionPoint} | ||
/> | ||
<div className="flex gap-2"> | ||
<KickButton | ||
{...member} | ||
roomId={roomId} | ||
/> | ||
<DelegationButton | ||
{...member} | ||
roomId={roomId} | ||
/> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default MemberTab; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<> | ||
<h1 className={headingStyle}> | ||
<p className="font-bold">방에 혼자 남았을 때만</p> | ||
<p className="font-bold">삭제할 수 있어요.</p> | ||
</h1> | ||
</> | ||
); | ||
} else { | ||
return ( | ||
<> | ||
<h1 className={headingStyle}>방을 삭제할까요?</h1> | ||
<section className="mt-4 flex flex-col gap-2"> | ||
<label | ||
htmlFor="confirm" | ||
className={descriptionStyle} | ||
> | ||
방의 이름 "{room.title}" 을 적어주세요. | ||
</label> | ||
<Input | ||
id="confirm" | ||
value={confirmInput} | ||
autoComplete="off" | ||
placeholder={room.title} | ||
onChange={(e) => setConfirmInput(e.target.value)} | ||
/> | ||
</section> | ||
{error && ( | ||
<p className="ml-2 mt-2 text-base text-danger"> | ||
{error.response?.data.message} | ||
</p> | ||
)} | ||
<button | ||
className="btn btn-danger mt-8 flex w-full items-start justify-center" | ||
onClick={handleRemove} | ||
disabled={confirmInput !== room.title || isPending} | ||
> | ||
{isPending ? <LoadingSpinner size="2xl" /> : '방 삭제'} | ||
</button> | ||
</> | ||
); | ||
} | ||
}; | ||
|
||
export default RemoveTab; | ||
|
||
// 헤딩에 적용할 스타일 | ||
const headingStyle = 'text-xl font-bold'; | ||
|
||
// 회색으로 간결한 설명을 적을 때 적용할 스타일 | ||
const descriptionStyle = 'text-base text-dark-gray'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p1; onChange에 들어가는 함수는 따로 함수를 선언해서 쓰면 어떨까요?
https://www.notion.so/prgrms/FE-568228d7d8f64051b5652548944214e9?pvs=4#e81e072bdf404604862aee5f488e1de4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇.. 요기선 세터 함수를 호출하는 한 줄의 로직을 수행하는 것이라 구현이 드러나는 방법을 선택했어요!!
(Input 컴포넌트의 동작을 바로 확인할 수 있게 colocation 하는 느낌?)
물론
onChange
동작이 몇 줄 단위로 복잡해진다면, 다른 핸들러 함수로 빼는 것이 좋겠지만.. 한 줄 로직도 빼는것이 좋을까요!?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 그것도 괜찮네요! 한 줄 일 때는 그대로 코드를 작성하는 걸로 할까요? 💡