-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 채팅 UI 작업 #232
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] 채팅 UI 작업 #232
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| 'use client'; | ||
| import { useEffect, useRef, useState } from 'react'; | ||
|
|
||
| import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; | ||
|
|
||
| import { ChatHeader, ChatInput, MyChat, OtherChat } from '@/components/pages/chat'; | ||
|
|
||
| // 임시 데이터 | ||
| const data = Array.from({ length: 30 }, (_, index) => ({ | ||
| id: index + 1, | ||
| text: '안녕하세요 멍선생입니다 계속 입력하면 어떻게 되나 봅시다', | ||
| time: '오후 11:24', | ||
| profileImage: DEFAULT_PROFILE_IMAGE, | ||
| nickName: '흰둥이', | ||
| userId: index % 3 === 0 ? 0 : 1, | ||
| })); | ||
|
|
||
| const myId = 0; | ||
|
|
||
| const ChatRoomPage = () => { | ||
| const [messages, setMessages] = useState(data); | ||
|
|
||
| const handleSubmit = (text: string) => { | ||
| const newMessage = { | ||
| id: Date.now(), | ||
| text, | ||
| profileImage: DEFAULT_PROFILE_IMAGE, | ||
| nickName: '흰둥이', | ||
| time: '지금', | ||
| userId: myId, | ||
| }; | ||
|
|
||
| setMessages((prev) => [...prev, newMessage]); | ||
| }; | ||
|
|
||
| const bottomRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| // 마지막 메세지 ref로 스크롤 이동 | ||
| useEffect(() => { | ||
| bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); | ||
| }, [messages]); | ||
|
|
||
| return ( | ||
| <div className='flex h-[calc(100vh-112px)] flex-col'> | ||
| <ChatHeader /> | ||
|
|
||
| <div className='scrollbar-thin ml-4 flex-1 overflow-y-auto pt-4'> | ||
| {messages.map((item) => | ||
| item.userId === myId ? ( | ||
| <MyChat key={item.id} item={item} /> | ||
| ) : ( | ||
| <OtherChat key={item.id} item={item} /> | ||
| ), | ||
| )} | ||
| <div ref={bottomRef} /> | ||
| </div> | ||
|
|
||
| <ChatInput onSubmit={handleSubmit} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ChatRoomPage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| 'use client'; | ||
|
|
||
| import { useRouter } from 'next/navigation'; | ||
|
|
||
| import { Icon } from '@/components/icon'; | ||
|
|
||
| export const ChatHeader = () => { | ||
| 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'> | ||
| <Icon | ||
| id='chevron-left-2' | ||
| className='w-6 cursor-pointer text-gray-500' | ||
| onClick={() => router.back()} | ||
| /> | ||
| <span className='text-text-md-bold text-gray-800'>분당 보드게임 동아리</span> | ||
| <div className='w-6' /> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| ' use client'; | ||
| import { useRef, useState } from 'react'; | ||
|
|
||
| import { Icon } from '@/components/icon'; | ||
|
|
||
| interface IProps { | ||
| onSubmit: (text: string) => void; | ||
| } | ||
|
|
||
| export const ChatInput = ({ onSubmit }: IProps) => { | ||
| const [message, setMessage] = useState(''); | ||
| const inputRef = useRef<HTMLTextAreaElement>(null); | ||
|
|
||
| // Enter 키로 전송, Shift + Enter 로 줄바꿈 | ||
| const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { | ||
| if (e.key === 'Enter' && !e.shiftKey) { | ||
| e.preventDefault(); | ||
| handleSubmit(); | ||
| } | ||
| }; | ||
|
|
||
| const handleSubmit = () => { | ||
| const changedMessage = message.trim(); | ||
|
|
||
| if (!changedMessage) { | ||
| inputRef.current?.focus(); | ||
| setMessage(''); | ||
| return; | ||
| } | ||
|
|
||
| onSubmit(changedMessage); | ||
| setMessage(''); | ||
| inputRef.current?.focus(); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className='flex w-full justify-center px-5'> | ||
| <div className='relative w-full max-w-110'> | ||
| <textarea | ||
| ref={inputRef} | ||
| className='bg-mono-white text-text-md-medium w-full resize-none rounded-2xl border border-gray-300 px-4 py-4 text-gray-800 placeholder:text-gray-500 focus:outline-none [&::-webkit-scrollbar]:hidden' | ||
| placeholder='메세지를 입력해주세요.' | ||
| rows={1} | ||
| value={message} | ||
| onChange={(e) => setMessage(e.target.value)} | ||
| onKeyDown={handleKeyDown} | ||
| /> | ||
| <button className='absolute top-4 right-5 h-6 w-6' aria-label='검색 실행' type='button'> | ||
|
||
| <Icon id='send' className='cursor-pointer text-gray-500' onClick={handleSubmit} /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| interface IProps { | ||
| item: { | ||
| id: number; | ||
| nickName: string; | ||
| profileImage: string; | ||
| text: string; | ||
| time: string; | ||
| }; | ||
| } | ||
|
|
||
| export const MyChat = ({ item }: IProps) => { | ||
| const { text, time } = item; | ||
| return ( | ||
| <div className='mr-3 mb-5 flex justify-end'> | ||
| <div className='text-text-2xs-regular flex items-end py-1 text-gray-500'>{time}</div> | ||
| <div className='ml-1.5 flex flex-col'> | ||
| <span className='bg-mint-200 mt-1 max-w-60 rounded-tl-2xl rounded-tr-sm rounded-br-2xl rounded-bl-2xl px-4 py-3 text-gray-800'> | ||
| {text} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import Image from 'next/image'; | ||
|
|
||
| interface IProps { | ||
| item: { | ||
| id: number; | ||
| nickName: string; | ||
| profileImage: string; | ||
| text: string; | ||
| time: string; | ||
| }; | ||
| } | ||
|
|
||
| export const OtherChat = ({ item }: IProps) => { | ||
| const { nickName, profileImage, text, time } = item; | ||
| return ( | ||
| <div className='mb-5 flex'> | ||
| <Image | ||
| width={40} | ||
| className='mr-3 size-10 rounded-full object-cover' | ||
| alt='프로필 이미지' | ||
| height={40} | ||
| src={profileImage} | ||
| /> | ||
| <div className='mr-1.5 flex flex-col'> | ||
| <span className='text-text-xs-medium text-gray-800'>{nickName}</span> | ||
| <span className='bg-mono-white mt-1 max-w-60 rounded-tl-sm rounded-tr-2xl rounded-br-2xl rounded-bl-2xl px-4 py-3 text-gray-800'> | ||
| {text} | ||
| </span> | ||
| </div> | ||
| <div className='text-text-2xs-regular flex items-end py-1 text-gray-500'>{time}</div> | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| 'use client'; | ||
| import Image from 'next/image'; | ||
| import { useRouter } from 'next/navigation'; | ||
|
|
||
| import { DEFAULT_PROFILE_IMAGE } from 'constants/default-images'; | ||
|
|
||
| import { cn } from '@/lib/utils'; | ||
|
|
||
| const dummy = [ | ||
| { | ||
| id: 1, | ||
|
|
@@ -39,12 +39,17 @@ const dummy = [ | |
| ]; | ||
|
|
||
| export const Chat = () => { | ||
| const router = useRouter(); | ||
| const handleClick = () => { | ||
| router.push('/chat/1'); | ||
| }; | ||
|
Comment on lines
+43
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 채팅방 ID를 동적으로 사용해야 합니다. 현재 모든 채팅 아이템을 클릭하면 🔎 제안하는 수정 사항- const handleClick = () => {
- router.push('/chat/1');
+ const handleClick = (id: number) => {
+ router.push(`/chat/${id}`);
};그리고 Line 52의 onClick도 수정이 필요합니다: - onClick={handleClick}
+ onClick={() => handleClick(item.id)}
🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 나중에 수정할 예정입니다 임시 테스트에요ㅜㅜ
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wooktori 네, 알겠습니다! 임시 테스트 코드라면 나중에 수정하셔도 괜찮습니다. 😊 나중에 놓치지 않도록 이 내용으로 후속 이슈를 생성해 드릴까요? 원하시면 말씀해 주세요! |
||
| return ( | ||
| <ul className='flex flex-col'> | ||
| {dummy.map((item) => ( | ||
| <li | ||
| key={item.id} | ||
| className='flex cursor-pointer items-center gap-3 bg-white p-5 transition hover:bg-gray-50' | ||
| onClick={handleClick} | ||
| > | ||
| {/* 프로필 이미지 */} | ||
| <div className='relative size-12 overflow-hidden rounded-full'> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export { Chat } from './chat'; | ||
| export { ChatHeader } from './chat-header'; | ||
| export { ChatInput } from './chat-input'; | ||
| export { MyChat } from './chat-my-chat'; | ||
| export { OtherChat } from './chat-other-chat'; |
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.
앞에 공백 있어요