generated from yuminn-k/yuminnk-nextjs-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ feat : Add live chat functionality
- Chat icon in the video classroom space - Multi-user enabled confirmed Related Issue : #167
- Loading branch information
Showing
9 changed files
with
298 additions
and
16 deletions.
There are no files selected for viewing
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,5 @@ | ||
const URLList = { | ||
liveURL: 'http://43.203.66.25/api/gin/chat/stream/', | ||
}; | ||
|
||
export default URLList; |
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,30 @@ | ||
import URLList from './URLList'; | ||
import {NativeEventSource, EventSourcePolyfill} from 'event-source-polyfill'; | ||
import Cookies from 'js-cookie'; | ||
|
||
const getLiveMessages = ( | ||
scheduleId: number, | ||
onMessage: (message: string) => void | ||
) => { | ||
const token = Cookies.get('access_token'); | ||
const url = `${URLList.liveURL}${scheduleId}`; | ||
|
||
const EventSource = EventSourcePolyfill || NativeEventSource; | ||
const eventSource = new EventSource(url, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}); | ||
|
||
eventSource.onmessage = function (event) { | ||
onMessage(event.data); | ||
}; | ||
|
||
eventSource.onerror = function (event) { | ||
console.error('EventSource failed:', event); | ||
}; | ||
|
||
return eventSource; | ||
}; | ||
|
||
export default getLiveMessages; |
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 @@ | ||
import req from '../apiUtils'; | ||
|
||
const getMessages = async (scheduleId: number) => { | ||
const response = await req(`/chat/messages/${scheduleId}`, 'get', 'gin'); | ||
return response; | ||
}; | ||
|
||
export default getMessages; |
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,13 @@ | ||
import getLiveMessages from './getLiveMessages'; | ||
import getMessages from './getMessages'; | ||
import postMessage from './postMessage'; | ||
import URLList from './URLList'; | ||
|
||
const chatRoomAPI = { | ||
getMessages, | ||
postMessage, | ||
getLiveMessages, | ||
URLList, | ||
}; | ||
|
||
export default chatRoomAPI; |
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,21 @@ | ||
import req from '../apiUtils'; | ||
|
||
const postMessage = async ( | ||
scheduleId: number, | ||
user: string, | ||
message: string | ||
) => { | ||
const formData = new FormData(); | ||
formData.append('user', user); | ||
formData.append('message', message); | ||
const response = await req( | ||
`/chat/room/${scheduleId}`, | ||
'post', | ||
'gin', | ||
formData | ||
); | ||
|
||
return response.data; | ||
}; | ||
|
||
export default postMessage; |
171 changes: 171 additions & 0 deletions
171
src/app/classes/[cId]/[mId]/components/chatComponents/page.tsx
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,171 @@ | ||
'use client'; | ||
|
||
import {useRef, useState, useEffect} from 'react'; | ||
import {useParams} from 'next/navigation'; | ||
import Image from 'next/image'; | ||
import {useRecoilValue} from 'recoil'; | ||
import userState from '@/src/recoil/atoms/userState'; | ||
import chatRoomAPI from '@/src/api/chatRoom'; | ||
import getUserInfo from '@/src/api/classUser/getUserInfo'; | ||
import ChatInput from '@/src/app/classes/[cId]/[mId]/components/subComponents/ChatInput'; | ||
import {User} from '@/src/interfaces/user'; | ||
|
||
interface UserInfo extends User { | ||
id: number; | ||
} | ||
|
||
const ShowMain = () => { | ||
const user = useRecoilValue(userState) as User; | ||
const [message, setMessage] = useState(''); | ||
const [messages, setMessages] = useState<string[]>([]); | ||
const [chatUsers, setChatUsers] = useState<{[key: string]: User}>({}); | ||
const params = useParams(); | ||
const getClassId = Number(params.cId); | ||
const getScheduleId = 14; | ||
const messagesEndRef = useRef<HTMLDivElement>(null); | ||
|
||
const handleGetMessages = () => { | ||
chatRoomAPI | ||
.getMessages(getScheduleId) | ||
.then(res => { | ||
if (res && 'data' in res) { | ||
const {data} = res; | ||
setMessages(data || []); | ||
} else { | ||
console.error('응답에서 data를 찾을 수 없습니다.'); | ||
setMessages([]); | ||
} | ||
}) | ||
.catch(error => { | ||
console.error('메시지를 가져오는 중 오류 발생:', error); | ||
}); | ||
}; | ||
|
||
const handleGetLiveStart = () => { | ||
const eventSource = chatRoomAPI.getLiveMessages(getScheduleId, message => { | ||
setMessages(prevMessages => [...prevMessages, message]); | ||
}); | ||
|
||
eventSource.onerror = function (event) { | ||
console.error('EventSource failed:', event); | ||
}; | ||
|
||
return eventSource; | ||
}; | ||
|
||
const handleGetUserInfo = async (uid: number) => { | ||
const userInfo = await getUserInfo(uid, getClassId); | ||
return userInfo; | ||
}; | ||
|
||
useEffect(() => { | ||
if (message === '') return; | ||
chatRoomAPI | ||
.postMessage(getScheduleId, user.id.toString(), message) | ||
.then(res => { | ||
setMessage(''); | ||
console.log(res); | ||
}); | ||
}, [message]); | ||
|
||
useEffect(() => { | ||
const fetchChatUsers = async () => { | ||
const newChatUsers: {[id: string]: UserInfo} = {}; | ||
for (const msg of messages) { | ||
const [id] = msg.split(': '); | ||
if (!newChatUsers[id]) { | ||
newChatUsers[id] = await handleGetUserInfo(Number(id)); | ||
} | ||
} | ||
setChatUsers(newChatUsers); | ||
}; | ||
|
||
fetchChatUsers(); | ||
}, [messages]); | ||
|
||
useEffect(() => { | ||
if (messagesEndRef.current) { | ||
messagesEndRef.current.scrollIntoView({behavior: 'smooth'}); | ||
} | ||
}, [messages]); | ||
|
||
useEffect(() => { | ||
const eventSource = handleGetLiveStart(); | ||
return () => { | ||
eventSource.close(); | ||
}; | ||
}, [getScheduleId]); | ||
|
||
useEffect(() => { | ||
handleGetMessages(); | ||
}, [getScheduleId]); | ||
|
||
return ( | ||
<div className="flex justify-center mt-10"> | ||
<div className="w-full max-w-md"> | ||
<div className="w-full mt-2"> | ||
<div className="border-4 rounded-lg border-gray-500 p-2 h-[550px] overflow-auto"> | ||
<p className="text-center inline-block px-4 py-2 text-sm text-white bg-violet-300 rounded-lg w-full"> | ||
최상단 채팅 내용입니다 | ||
</p> | ||
{messages.map((msg, index) => { | ||
const [id, message] = msg.split(': '); | ||
const chatUser = chatUsers[id]; | ||
if (!chatUser) { | ||
return null; | ||
} | ||
return ( | ||
<div | ||
key={index} | ||
className={ | ||
Number(id) === user.id | ||
? 'flex justify-end items-start' | ||
: 'flex justify-start items-start' | ||
} | ||
> | ||
{Number(id) !== user.id && ( | ||
<div className="flex items-center mt-2"> | ||
<div className="rounded-full w-8 h-8 overflow-hidden max-w-20"> | ||
<Image | ||
src={chatUser.image || user.image} | ||
alt={'userImage'} | ||
width={40} | ||
height={40} | ||
/> | ||
</div> | ||
<div className="ml-2"> | ||
<p>{chatUser.nickname || user.name}</p> | ||
<p | ||
className={ | ||
'inline-block px-3 py-2 text-sm text-white bg-gray-500 rounded-lg max-w-72' | ||
} | ||
> | ||
{message} | ||
</p> | ||
</div> | ||
</div> | ||
)} | ||
{Number(id) === user.id && ( | ||
<div className="mt-2"> | ||
<p | ||
className={ | ||
'inline-block px-4 py-2 text-sm text-white bg-blue-400 rounded-lg max-w-72' | ||
} | ||
> | ||
{message} | ||
</p> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
})} | ||
<div ref={messagesEndRef} /> | ||
</div> | ||
</div> | ||
<ChatInput setMsg={setMessage} /> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ShowMain; |
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
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
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