Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
107 changes: 94 additions & 13 deletions src/app/chat/[roomId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useLayoutEffect, useRef, useState } from 'react';
import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images';

import { ChatHeader, ChatInput, MyChat, OtherChat } from '@/components/pages/chat';
import { UserList } from '@/components/pages/chat/chat-user-list';

// 임시 데이터
let data = Array.from({ length: 30 }, (_, index) => ({
Expand All @@ -27,8 +28,73 @@ data = [
];
const myId = 0;

// 임시 사용자 데이터
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: '한줄 소개 내용입니다.',
},
];

const ChatRoomPage = () => {
const [messages, setMessages] = useState(data);
const [isUserListOpen, setIsUserListOpen] = useState(false);

const handleSubmit = (text: string) => {
const newMessage = {
Expand Down Expand Up @@ -57,23 +123,38 @@ const ChatRoomPage = () => {
}, [messages]);

return (
<div className='flex h-[calc(100vh-112px)] flex-col'>
<ChatHeader />

<div className='relative h-[calc(100vh-112px)] overflow-hidden'>
{/* 채팅 화면 */}
<div
ref={containerRef}
className='scrollbar-thin ml-4 flex flex-1 flex-col gap-4 overflow-y-auto py-4'
className={`absolute inset-0 flex flex-col transition-transform duration-300 ease-in-out ${
isUserListOpen ? '-translate-x-full' : 'translate-x-0'
}`}
>
{messages.map((item) =>
item.userId === myId ? (
<MyChat key={item.id} item={item} />
) : (
<OtherChat key={item.id} item={item} />
),
)}
<ChatHeader onUserListClick={() => setIsUserListOpen(true)} />

<div
ref={containerRef}
className='scrollbar-thin ml-4 flex flex-1 flex-col gap-4 overflow-y-auto py-4'
>
{messages.map((item) =>
item.userId === myId ? (
<MyChat key={item.id} item={item} />
) : (
<OtherChat key={item.id} item={item} />
),
)}
</div>

<ChatInput onSubmit={handleSubmit} />
</div>

<ChatInput onSubmit={handleSubmit} />
<div
className={`absolute inset-0 transition-transform duration-300 ease-in-out ${
isUserListOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
<UserList users={users} onClose={() => setIsUserListOpen(false)} />
</div>
</div>
);
};
Expand Down
8 changes: 6 additions & 2 deletions src/components/pages/chat/chat-header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { useRouter } from 'next/navigation';

import { Icon } from '@/components/icon';

export const ChatHeader = () => {
interface ChatHeaderProps {
onUserListClick: () => void;
}

export const ChatHeader = ({ onUserListClick }: ChatHeaderProps) => {
const router = useRouter();
return (
<div className='bg-mono-white flex w-full items-center justify-between border-b border-gray-200 px-5 py-3'>
Expand All @@ -14,7 +18,7 @@ export const ChatHeader = () => {
onClick={() => router.back()}
/>
<span className='text-text-md-bold text-gray-800'>분당 보드게임 동아리</span>
<div className='w-6' />
<Icon id='users-1' className='w-6 cursor-pointer text-gray-500' onClick={onUserListClick} />
</div>
);
};
31 changes: 31 additions & 0 deletions src/components/pages/chat/chat-user-list/UserOutModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Button, ModalContent, ModalDescription, ModalTitle, useModal } from '@/components/ui';

interface IProps {
nickName: string;
}

export const UserOutModal = ({ nickName }: IProps) => {
const { close } = useModal();

const handleOut = () => {
console.log(`${nickName} 내보내기 완료`);
close();
};

return (
<ModalContent className='flex max-w-[311px] flex-col'>
<div className='my-6 flex flex-col items-center'>
<ModalTitle>{`${nickName}을 내보내시겠어요?`}</ModalTitle>
<ModalDescription>이 작업은 취소할 수 없습니다.</ModalDescription>
</div>
<div className='flex w-full gap-2'>
<Button className='text-text-sm-semibold h-10' variant='tertiary' onClick={close}>
취소
</Button>
<Button className='text-text-sm-bold h-10' onClick={handleOut}>
내보내기
</Button>
</div>
</ModalContent>
);
};
97 changes: 97 additions & 0 deletions src/components/pages/chat/chat-user-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use client';

import Image from 'next/image';

import { useState } from 'react';

import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images';

import { Icon } from '@/components/icon';
import { useModal } from '@/components/ui';

import { UserOutModal } from './UserOutModal';

interface User {
id: number;
nickName: string;
profileImage: string;
profileMessage?: string;
}

interface UserListProps {
onClose: () => void;
users: User[];
}

export const UserList = ({ onClose, users }: UserListProps) => {
const [isManaging, setIsManaging] = useState(false);
const { open } = useModal();

return (
<div className='bg-mono-white flex h-[calc(100vh-112px)] flex-col'>
{/* 헤더 */}
<div className='flex items-center justify-between border-b border-gray-200 px-5 py-3'>
<Icon id='chevron-left-2' className='w-6 cursor-pointer text-gray-500' onClick={onClose} />
<span className='text-text-md-bold flex items-center gap-1'>
<span className='text-gray-800'>참여자</span>
<span className='text-mint-500'>{users.length}</span>
</span>
<button
className='text-text-sm-semibold cursor-pointer'
onClick={() => setIsManaging(!isManaging)}
>
{isManaging ? (
<span className='text-gray-600'>완료</span>
) : (
<span className='text-mint-600'>관리</span>
)}
</button>
</div>

{/* 유저 리스트 */}
<div className='scrollbar-thin flex-1 overflow-y-auto'>
{users.map((user, index) => (
<div key={user.id}>
<div className='bg-mono-white flex h-22 items-center gap-4 p-5'>
<div className='h-12 w-12 overflow-hidden rounded-full'>
<Image
width={48}
className='h-full w-full object-cover'
alt='profile'
height={48}
src={user.profileImage || DEFAULT_PROFILE_IMAGE}
/>
</div>

<div className='flex-1'>
<div className='text-text-md-bold text-gray-800'>{user.nickName}</div>
<div className='text-text-sm-medium line-clamp-1 text-gray-600'>
{user.profileMessage || '상태 메시지가 없습니다.'}
</div>
</div>

{/* 방장이 0번째로 들어온다면 이렇게, 방장이라는걸 알 수 있는 필드가 있다면 수정 */}
{index === 0 ? (
<span className='bg-mint-100 text-mint-700 text-text-xs-medium rounded-full px-2.5 py-1'>
방장
</span>
) : null}

{isManaging && index !== 0 && (
<button
className='bg-error-500 flex h-5 w-5 items-center justify-center rounded-full'
onClick={(e) => {
e.stopPropagation();
open(<UserOutModal nickName={user.nickName} />);
}}
>
<div className='bg-mono-white h-0.5 w-2.5' />
</button>
)}
</div>
</div>
))}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/pages/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { ExpandableText } from './chat-long-text';
export { LongTextModal } from './chat-modal';
export { MyChat } from './chat-my-chat';
export { OtherChat } from './chat-other-chat';
export { UserList } from './chat-user-list';