-
Notifications
You must be signed in to change notification settings - Fork 2
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
🎉 채팅 관련 UI 및 로직 #96
🎉 채팅 관련 UI 및 로직 #96
Changes from all commits
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,13 @@ | ||
{ | ||
"extends": [ | ||
"development" | ||
], | ||
"hints": { | ||
"axe/structure": [ | ||
"default", | ||
{ | ||
"list": "off" | ||
} | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, { useState } from 'react' | ||
import Image from 'next/image' | ||
import Button from '@/components/ui/button' | ||
import Input from '@/components/ui/input' | ||
import Assets from '@/config/assets' | ||
|
||
const ChatInput = ({ | ||
onSubmit, | ||
}: { | ||
onSubmit: (_newMessage: string) => void | ||
}) => { | ||
const [newMessage, setNewMessage] = useState<string>('') | ||
|
||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
e.preventDefault() | ||
setNewMessage(e.target.value) | ||
} | ||
|
||
const onSubmitMessage = (e: React.FormEvent<HTMLFormElement>) => { | ||
e.preventDefault() | ||
if (!newMessage.trim()) return | ||
|
||
onSubmit(newMessage) | ||
setNewMessage('') | ||
} | ||
|
||
return ( | ||
<form | ||
onSubmit={onSubmitMessage} | ||
className="absolute bottom-0 grid items-center w-full grid-cols-5 p-4 align-middle h-chat_input" | ||
> | ||
<div className="col-span-1" /> | ||
<Input | ||
onChange={onChange} | ||
value={newMessage} | ||
type="text" | ||
placeholder="메세지를 입력하세요." | ||
className="col-span-3" | ||
/> | ||
<div className="flex justify-center col-span-1"> | ||
<Button | ||
type="submit" | ||
variant={null} | ||
className="transition-opacity hover:opacity-70" | ||
> | ||
<Image src={Assets.sendIcon} alt="발송" /> | ||
</Button> | ||
</div> | ||
</form> | ||
) | ||
} | ||
|
||
export default ChatInput |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React, { forwardRef, memo } from 'react' | ||
import { TYPOGRAPHY } from '@/styles/sizes' | ||
import type { Message } from '@/types/message' | ||
import { cn } from '@/utils' | ||
|
||
type ChatListProps = { | ||
messages: Message[] | ||
currentUserNickname?: string | ||
} | ||
|
||
type ChatProps = { | ||
message: Message | ||
isMyMessage: boolean | ||
} | ||
|
||
const ChatList = forwardRef<HTMLDivElement, ChatListProps>( | ||
({ messages, currentUserNickname }, ref) => { | ||
return ( | ||
<ul className="flex flex-col w-full h-full gap-1"> | ||
{messages.map((message: Message) => { | ||
return ( | ||
<Chat | ||
key={message.id} | ||
message={message} | ||
isMyMessage={message.sender === currentUserNickname} | ||
/> | ||
) | ||
})} | ||
{messages.length === 0 && ( | ||
<h1 className="text-red-500">채팅을 시작해보세요!</h1> | ||
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. 채팅방에 진입했을 때 메세지들이 있는데도 채팅을 시작해보세요 문구가 1초정도 보였다가 사라지는데 혹시 의도하신 건가요? 🤔 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. 아닙니다. api처리하면서 수정할 예정입니다! |
||
)} | ||
<div className="invisible" ref={ref} /> | ||
</ul> | ||
) | ||
}, | ||
) | ||
ChatList.displayName = 'ChatList' | ||
|
||
export default memo(ChatList) | ||
|
||
const Chat = ({ message, isMyMessage }: ChatProps) => { | ||
return isMyMessage ? ( | ||
<MyChat message={message} /> | ||
) : ( | ||
<OtherChat message={message} /> | ||
) | ||
} | ||
|
||
const MyChat = ({ message }: Pick<ChatProps, 'message'>) => { | ||
return ( | ||
<li className={cn('flex flex-row gap-2 justify-end')}> | ||
<p className={cn('mt-auto break-keep', TYPOGRAPHY.description)}> | ||
{message.createdAt.toDate().toLocaleTimeString().slice(0, -3)} | ||
</p> | ||
<div | ||
className={ | ||
'p-2 text-black rounded-lg max-w-[75%] w-fit bg-background-secondary-color' | ||
} | ||
> | ||
{message.text} | ||
</div> | ||
</li> | ||
) | ||
} | ||
|
||
const OtherChat = ({ message }: Pick<ChatProps, 'message'>) => { | ||
return ( | ||
<li className={cn('flex flex-row gap-2 mr-auto justify-start')}> | ||
<div | ||
className={'p-2 text-white rounded-lg max-w-[75%] bg-primary-color '} | ||
> | ||
{message.text} | ||
</div> | ||
<p className={cn('mt-auto break-keep', TYPOGRAPHY.description)}> | ||
{message.createdAt.toDate().toLocaleTimeString().slice(0, -3)} | ||
</p> | ||
</li> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,64 @@ | ||
'use client' | ||
|
||
import React from 'react' | ||
import { collection, limit, orderBy, query, addDoc } from 'firebase/firestore' | ||
import useFirestoreQuery, { Message } from '@/hooks/useFirestoreQuery' | ||
import { db, getMessageRef } from '@/lib/firebase' | ||
import React, { useEffect, useRef } from 'react' | ||
import { limit, orderBy, query, addDoc } from 'firebase/firestore' | ||
import PageTitle from '@/components/domain/page-title' | ||
import { CHAT_LIMIT } from '@/config/firebaseConfig' | ||
import { useAuth } from '@/contexts/AuthProvider' | ||
import useFirestoreQuery from '@/hooks/useFirestoreQuery' | ||
import { useToast } from '@/hooks/useToast' | ||
import { getMessageRef } from '@/lib/firebase' | ||
import ChatInput from './components/ChatInput' | ||
import ChatList from './components/ChatList' | ||
|
||
const ChatPage = () => { | ||
const messageRef = getMessageRef('room2') | ||
const { currentUser } = useAuth() | ||
const { toast } = useToast() | ||
|
||
const messageRef = getMessageRef('room2') // TODO: room id를 받아서 처리 | ||
const messages = useFirestoreQuery( | ||
query(messageRef, orderBy('createdAt', 'asc'), limit(200)), | ||
query(messageRef, orderBy('createdAt', 'asc'), limit(CHAT_LIMIT)), | ||
oaoong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
const [newMessage, setNewMessage] = React.useState<string>('') | ||
const chatBottomRef = useRef<HTMLDivElement>(null) | ||
|
||
useEffect(() => { | ||
if (chatBottomRef.current) { | ||
chatBottomRef.current.scrollIntoView({ behavior: 'smooth' }) | ||
} | ||
}, [messageRef]) | ||
|
||
const onSubmitMessage = async (message: string) => { | ||
chatBottomRef.current?.scrollIntoView({ behavior: 'smooth' }) | ||
|
||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
e.preventDefault() | ||
setNewMessage(e.target.value) | ||
try { | ||
await addDoc(messageRef, { | ||
text: message, | ||
sender: currentUser?.nickname ?? '익명', | ||
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. 익명이 필요한 경우가 있나요?? 채팅같은 경우 무조건 로그인 상태여야만 접근할 수 있다면 필요 없을 것 같아서요 (잘 모름) 🤔 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. 필요 없을 것 같습니다. 권한 처리하면서 처리 예정입니다! |
||
createdAt: new Date(), | ||
}) | ||
} catch (e) { | ||
toast({ | ||
title: '메세지 전송에 실패했습니다.', | ||
variant: 'destructive', | ||
duration: 3000, | ||
}) | ||
} | ||
} | ||
|
||
return ( | ||
<div className="flex flex-col items-center w-full gap-10"> | ||
<h1 className="text-4xl">chat</h1> | ||
<h1>new message</h1> | ||
<form | ||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => { | ||
e.preventDefault() | ||
console.log(newMessage) | ||
addDoc(collection(db, 'chats', 'room2', 'messages'), { | ||
text: newMessage, | ||
sender: 'me', | ||
createdAt: new Date().toISOString(), | ||
}).then(() => { | ||
setNewMessage('') | ||
}) | ||
}} | ||
> | ||
<input | ||
onChange={onChange} | ||
value={newMessage} | ||
type="text" | ||
placeholder="메세지를 입력하세요." | ||
<main className="relative flex flex-col items-center w-full gap-10 h-page pb-chat_input"> | ||
<PageTitle title="채팅방" /> | ||
<section className="flex flex-col items-center px-2 overflow-scroll overflow-x-hidden"> | ||
<ChatList | ||
messages={messages} | ||
currentUserNickname={currentUser?.nickname} | ||
ref={chatBottomRef} | ||
/> | ||
<button type="submit">send</button> | ||
</form> | ||
<div className="flex flex-col items-center w-full"> | ||
{messages.map((item: Message, idx: number) => { | ||
console.log(item) | ||
return ( | ||
<div key={idx} className="flex flex-row gap-2"> | ||
<h2>{`${item.sender}:`} </h2> | ||
<h2>{item.text}</h2> | ||
</div> | ||
) | ||
})} | ||
{messages.length === 0 && <h1 className="text-red-500">no data</h1>} | ||
</div> | ||
</div> | ||
</section> | ||
<ChatInput onSubmit={onSubmitMessage} /> | ||
<div className="w-full h-0 border border-t-background-secondary-color" /> | ||
</main> | ||
) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const firebaseConfig = { | ||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, | ||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, | ||
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, | ||
storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET, | ||
messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID, | ||
appId: process.env.NEXT_PUBLIC_APP_ID, | ||
} | ||
|
||
const CHAT_LIMIT = 200 //TODO: 제한 개수 수정 또는 불러오는 법? | ||
|
||
export default firebaseConfig | ||
export { CHAT_LIMIT } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Timestamp } from 'firebase/firestore' | ||
|
||
interface Message { | ||
text: string | ||
createdAt: Timestamp | ||
sender: string | ||
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. sender에는 유저의 nickName이 들어가는 것으로 이해하면 될까요? 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: string | ||
} | ||
|
||
export type { Message } |
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.
혹시 이건 무슨 파일이죠?
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.
webhint 하나 비활성화해서 저장됐나보네요