diff --git a/src/api/service/chat-service/index.ts b/src/api/service/chat-service/index.ts index a6e01732..421a9e2f 100644 --- a/src/api/service/chat-service/index.ts +++ b/src/api/service/chat-service/index.ts @@ -4,7 +4,12 @@ import { CreateDMPayloads, GetChatMessagesParams, GetChatMessagesResponse, + GetChatRoomParams, + getChatRoomResponse, GetChatRoomsResponse, + GetParticipantsParams, + GetParticipantsResponse, + KickUserPayloads, ReadMessagesParams, ReadMessagesResponse, } from '@/types/service/chat'; @@ -34,4 +39,19 @@ export const chatServiceRemote = () => ({ readMessages: async ({ roomId }: ReadMessagesParams) => { return apiV1.put(`/chat/rooms/${roomId}/read`); }, + + // 채팅방 상세 조회 + getChatRoom: async ({ roomId }: GetChatRoomParams) => { + return apiV1.get(`/chat/rooms/${roomId}`); + }, + + // 참여자 목록 조회 + getParticipants: async ({ roomId }: GetParticipantsParams) => { + return apiV1.get(`/chat/rooms/${roomId}/participants`); + }, + + // 추방하기 + kickUser: async (roomId: number, payload: KickUserPayloads) => { + return apiV1.post(`/chat/rooms/${roomId}/kick`, payload); + }, }); diff --git a/src/app/message/chat/[roomId]/ChatRoomPage.tsx b/src/app/message/chat/[roomId]/ChatRoomPage.tsx index 193e3ba1..8d5ac356 100644 --- a/src/app/message/chat/[roomId]/ChatRoomPage.tsx +++ b/src/app/message/chat/[roomId]/ChatRoomPage.tsx @@ -1,79 +1,19 @@ 'use client'; -import { useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; +import { useRouter } from 'next/navigation'; + +import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { ChatHeader, ChatInput, MyChat, OtherChat } from '@/components/pages/chat'; import { UserList } from '@/components/pages/chat/chat-user-list'; -import { useGetChatMessages } from '@/hooks/use-chat'; -import { useReadMessages } from '@/hooks/use-chat/use-chat-read'; -import { useChatSocket } from '@/hooks/use-chat/use-chat-socket'; +import { + useChatSocket, + useGetChatMessages, + useGetChatRoom, + useReadMessages, +} from '@/hooks/use-chat'; import { ChatMessage } from '@/types/service/chat'; -// 임시 사용자 데이터 -const users = [ - { - id: 0, - nickName: '멍선생', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 1, - nickName: '짱구', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 2, - nickName: '맹구', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 3, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다아ㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏ.', - }, - { - id: 4, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 5, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 6, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 7, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 8, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, - { - id: 9, - nickName: '철수', - profileImage: DEFAULT_PROFILE_IMAGE, - profileMessage: '한줄 소개 내용입니다.', - }, -]; - interface IProps { accessToken: string | null; roomId: number; @@ -81,9 +21,11 @@ interface IProps { } const ChatRoomPage = ({ accessToken, roomId, userId }: IProps) => { + const router = useRouter(); const [isUserListOpen, setIsUserListOpen] = useState(false); const [chatMessages, setChatMessages] = useState([]); + const { data: chatInfo } = useGetChatRoom(roomId); const { data: previousMessages } = useGetChatMessages(roomId); const { mutate: readMessages } = useReadMessages(roomId, userId); const { @@ -98,6 +40,14 @@ const ChatRoomPage = ({ accessToken, roomId, userId }: IProps) => { console.log('새 메시지:', message); setChatMessages((prev) => [...prev, message]); }, + // 백엔드 로직 확인 필요.(동작 X) + onNotification: (notification) => { + console.log(notification); + if (notification.type === 'KICKED' && notification.chatRoomId === roomId) { + alert('채팅방에서 추방되었습니다.'); + router.replace('/'); + } + }, }); useEffect(() => { @@ -112,8 +62,6 @@ const ChatRoomPage = ({ accessToken, roomId, userId }: IProps) => { setChatMessages([...previousMessages.messages].reverse()); }, [previousMessages]); - console.log(newMessages); - const handleSubmit = (text: string) => { sendMessage(text); }; @@ -139,7 +87,10 @@ const ChatRoomPage = ({ accessToken, roomId, userId }: IProps) => { isUserListOpen ? '-translate-x-full' : 'translate-x-0' }`} > - setIsUserListOpen(true)} /> + setIsUserListOpen(true)} + />
{ isUserListOpen ? 'translate-x-0' : 'translate-x-full' }`} > - setIsUserListOpen(false)} /> + setIsUserListOpen(false)} + />
); diff --git a/src/components/pages/chat/chat-header/index.tsx b/src/components/pages/chat/chat-header/index.tsx index 5ee7a4ff..4e515a72 100644 --- a/src/components/pages/chat/chat-header/index.tsx +++ b/src/components/pages/chat/chat-header/index.tsx @@ -6,9 +6,10 @@ import { Icon } from '@/components/icon'; interface ChatHeaderProps { onUserListClick: () => void; + title: string | undefined; } -export const ChatHeader = ({ onUserListClick }: ChatHeaderProps) => { +export const ChatHeader = ({ title, onUserListClick }: ChatHeaderProps) => { const router = useRouter(); return (
@@ -17,7 +18,7 @@ export const ChatHeader = ({ onUserListClick }: ChatHeaderProps) => { className='w-6 cursor-pointer text-gray-500' onClick={() => router.back()} /> - 분당 보드게임 동아리 + {title}
); diff --git a/src/components/pages/chat/chat-user-list/UserOutModal.tsx b/src/components/pages/chat/chat-user-list/UserOutModal.tsx index 559da41f..ebe08458 100644 --- a/src/components/pages/chat/chat-user-list/UserOutModal.tsx +++ b/src/components/pages/chat/chat-user-list/UserOutModal.tsx @@ -1,15 +1,25 @@ import { Button, ModalContent, ModalDescription, ModalTitle, useModal } from '@/components/ui'; +import { useKickUser } from '@/hooks/use-chat'; interface IProps { nickName: string; + roomId: number; + userId: number; } -export const UserOutModal = ({ nickName }: IProps) => { +export const UserOutModal = ({ nickName, roomId, userId }: IProps) => { const { close } = useModal(); - const handleOut = () => { - console.log(`${nickName} 내보내기 완료`); - close(); + const { mutateAsync } = useKickUser(roomId); + + const handleOut = async () => { + try { + await mutateAsync({ targetUserId: userId }); + console.log(`${nickName} 내보내기 완료`); + close(); + } catch (e) { + console.error(e); + } }; return ( diff --git a/src/components/pages/chat/chat-user-list/index.tsx b/src/components/pages/chat/chat-user-list/index.tsx index 86e68a53..3608adde 100644 --- a/src/components/pages/chat/chat-user-list/index.tsx +++ b/src/components/pages/chat/chat-user-list/index.tsx @@ -8,25 +8,21 @@ import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; import { Icon } from '@/components/icon'; import { useModal } from '@/components/ui'; +import { useGetParticipants } from '@/hooks/use-chat'; import { UserOutModal } from './UserOutModal'; -interface User { - id: number; - nickName: string; - profileImage: string; - profileMessage?: string; -} - interface UserListProps { onClose: () => void; - users: User[]; + roomId: number; + roomType: 'DM' | 'GROUP'; } -export const UserList = ({ onClose, users }: UserListProps) => { +export const UserList = ({ onClose, roomId, roomType }: UserListProps) => { const [isManaging, setIsManaging] = useState(false); const { open } = useModal(); - + const { data } = useGetParticipants(roomId); + console.log(roomType); return (
{/* 헤더 */} @@ -34,24 +30,28 @@ export const UserList = ({ onClose, users }: UserListProps) => { 참여자 - {users.length} + {data?.totalCount} - + {roomType === 'GROUP' ? ( + + ) : ( +
+ )}
{/* 유저 리스트 */}
- {users.map((user, index) => ( -
+ {data?.participants.map((user, index) => ( +
{
{user.nickName}
- {user.profileMessage || '상태 메시지가 없습니다.'} + {user.userId || '상태 메시지가 없습니다.'}
{/* 방장이 0번째로 들어온다면 이렇게, 방장이라는걸 알 수 있는 필드가 있다면 수정 */} - {index === 0 ? ( + {roomType === 'GROUP' && index === 0 ? ( 방장 @@ -82,7 +82,13 @@ export const UserList = ({ onClose, users }: UserListProps) => { className='bg-error-500 flex h-5 w-5 items-center justify-center rounded-full' onClick={(e) => { e.stopPropagation(); - open(); + open( + , + ); }} >
diff --git a/src/hooks/use-chat/index.ts b/src/hooks/use-chat/index.ts index 375e4e2b..9eb4c3fa 100644 --- a/src/hooks/use-chat/index.ts +++ b/src/hooks/use-chat/index.ts @@ -1,6 +1,10 @@ +export { useGetChatRoom } from './use-chat-detail'; export { useCreateDMChat } from './use-chat-dm'; +export { useKickUser } from './use-chat-kick'; export { useGetChatList } from './use-chat-list'; export { useChatListSocket } from './use-chat-list-socket'; export { useLongText } from './use-chat-longText'; export { useGetChatMessages } from './use-chat-messages'; +export { useGetParticipants } from './use-chat-participants'; +export { useReadMessages } from './use-chat-read'; export { useChatSocket } from './use-chat-socket'; diff --git a/src/hooks/use-chat/use-chat-detail/index.ts b/src/hooks/use-chat/use-chat-detail/index.ts new file mode 100644 index 00000000..e78a0376 --- /dev/null +++ b/src/hooks/use-chat/use-chat-detail/index.ts @@ -0,0 +1,11 @@ +import { useQuery } from '@tanstack/react-query'; + +import { API } from '@/api'; + +export const useGetChatRoom = (roomId: number) => { + const query = useQuery({ + queryKey: ['chatRoom', roomId], + queryFn: () => API.chatService.getChatRoom({ roomId }), + }); + return query; +}; diff --git a/src/hooks/use-chat/use-chat-kick/index.ts b/src/hooks/use-chat/use-chat-kick/index.ts new file mode 100644 index 00000000..45780530 --- /dev/null +++ b/src/hooks/use-chat/use-chat-kick/index.ts @@ -0,0 +1,19 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import { API } from '@/api'; +import { KickUserPayloads } from '@/types/service/chat'; + +export const useKickUser = (roomId: number) => { + const queryClient = useQueryClient(); + + const query = useMutation({ + mutationKey: ['participants', roomId], + mutationFn: (payloads: KickUserPayloads) => API.chatService.kickUser(roomId, payloads), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['participants', roomId], + }); + }, + }); + return query; +}; diff --git a/src/hooks/use-chat/use-chat-participants/index.ts b/src/hooks/use-chat/use-chat-participants/index.ts new file mode 100644 index 00000000..03a63a4a --- /dev/null +++ b/src/hooks/use-chat/use-chat-participants/index.ts @@ -0,0 +1,11 @@ +import { useQuery } from '@tanstack/react-query'; + +import { API } from '@/api'; + +export const useGetParticipants = (roomId: number) => { + const query = useQuery({ + queryKey: ['participants', roomId], + queryFn: () => API.chatService.getParticipants({ roomId }), + }); + return query; +}; diff --git a/src/types/service/chat.ts b/src/types/service/chat.ts index 756b06b3..94e2660d 100644 --- a/src/types/service/chat.ts +++ b/src/types/service/chat.ts @@ -9,16 +9,7 @@ export interface ChattingRoom { senderName: string; timestamp: string; }; - participants: [ - { - participantId: number; - userId: number; - nickName: string; - profileImage: string; - status: 'ACTIVE' | 'INACTIVE'; // 확인 필요💥💥 - joinedAt: string; - }, - ]; + participants: ChatUser[]; unreadCount: number; } @@ -33,6 +24,15 @@ export interface ChatMessage { createdAt?: string; } +export interface ChatUser { + joinedAt: string; + nickName: string; + participantId: 77; + profileImage: string; + status: 'ACTIVE' | 'INACTIVE'; // 확인 필요💥💥 + userId: number; +} + export interface GetChatRoomsResponse { chatRooms: ChattingRoom[]; } @@ -62,3 +62,35 @@ export interface ReadMessagesResponse { lastReadMessageId: number; unreadCount: number; } + +export interface GetChatRoomParams { + roomId: number; +} + +export interface getChatRoomResponse { + chatRoomId: number; + chatRoomName: string; + chatType: string; + createdAt: string; + groupId: number | null; + participantCount: number; + participants: ChatUser[]; +} + +export interface GetParticipantsParams { + roomId: number; +} + +export interface GetParticipantsResponse { + chatRoomId: number; + totalCount: number; + participants: ChatUser[]; +} + +export interface KickUserParams { + roomId: number; +} + +export interface KickUserPayloads { + targetUserId: number; +}