feat: 채팅 기능 추가 (임시)
This commit is contained in:
parent
0c93c240ea
commit
435faa9a74
7
frontend/package-lock.json
generated
7
frontend/package-lock.json
generated
@ -8,6 +8,7 @@
|
||||
"name": "edufocus",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"@tanstack/react-query": "^5.49.2",
|
||||
"axios": "^1.7.2",
|
||||
"react": "^18.3.1",
|
||||
@ -1309,6 +1310,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stomp/stompjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz",
|
||||
"integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"test:run": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": "^7.0.0",
|
||||
"@tanstack/react-query": "^5.49.2",
|
||||
"axios": "^1.7.2",
|
||||
"react": "^18.3.1",
|
||||
|
@ -6,6 +6,8 @@ import NotFoundPage from './pages/NotFoundPage';
|
||||
import { lazy } from 'react';
|
||||
import MyPageLayout from './components/Layout/MyPageLayout';
|
||||
import LearningLecturesPage from './pages/LearningLecturesPage/LearningLecturesPage';
|
||||
import ChatRoom from './components/ChatRoom/ChatRoom';
|
||||
import { MaxWidthLayout } from './components/Layout';
|
||||
|
||||
const LectureLayout = lazy(async () => await import('./components/Layout/LectureLayout'));
|
||||
const LearningLectureDetailPage = lazy(async () => await import('./pages/LearningLectureDetailPage'));
|
||||
@ -36,6 +38,17 @@ const router = createBrowserRouter([
|
||||
path: 'lecture/:lectureId/info',
|
||||
element: <LectureInfoPage />,
|
||||
},
|
||||
{
|
||||
// TODO: 채팅 분리
|
||||
path: 'chat/:lectureId',
|
||||
element: (
|
||||
<MaxWidthLayout>
|
||||
<main>
|
||||
<ChatRoom />
|
||||
</main>
|
||||
</MaxWidthLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'lecture/:lectureId',
|
||||
element: <LectureLayout />,
|
||||
|
10
frontend/src/assets/icons/send.svg
Normal file
10
frontend/src/assets/icons/send.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Send" clip-path="url(#clip0_881_1630)">
|
||||
<path id="Icon" d="M14.6666 1.3335L7.33325 8.66683M14.6666 1.3335L9.99992 14.6668L7.33325 8.66683M14.6666 1.3335L1.33325 6.00016L7.33325 8.66683" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_881_1630">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 471 B |
77
frontend/src/components/ChatRoom/ChatRoom.jsx
Normal file
77
frontend/src/components/ChatRoom/ChatRoom.jsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import styles from './ChatRoom.module.css';
|
||||
import { chatClient } from '../../utils/chat/chatClient';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import SendIcon from '/src/assets/icons/send.svg?react';
|
||||
|
||||
const USER_ID = crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
|
||||
// TODO: 채팅 훅 분리
|
||||
export default function ChatRoom() {
|
||||
const { lectureId } = useParams();
|
||||
const client = chatClient;
|
||||
const [messages, setMessages] = useState([]);
|
||||
const inputRef = useRef(null);
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const text = inputRef.current.value;
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
chatClient.publish({
|
||||
destination: `/pub/message/${lectureId}`,
|
||||
body: JSON.stringify({
|
||||
userId: USER_ID,
|
||||
name: '홍길동',
|
||||
content: text,
|
||||
}),
|
||||
});
|
||||
inputRef.current.value = '';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
client.onConnect = () => {
|
||||
client.subscribe(`/sub/channel/${lectureId}`, (response) => {
|
||||
const data = JSON.parse(response.body);
|
||||
const message = data.content;
|
||||
setMessages((prev) => [...prev, { id: prev.length, text: message, isMine: USER_ID === data.userId }]);
|
||||
});
|
||||
};
|
||||
client.activate();
|
||||
|
||||
return () => {
|
||||
client.deactivate();
|
||||
};
|
||||
}, [client, lectureId]);
|
||||
|
||||
return (
|
||||
<section className={styles.room}>
|
||||
<h2 className={styles.title}>채팅</h2>
|
||||
<ol className={styles.bubbles}>
|
||||
{messages.map((message) => (
|
||||
<li
|
||||
key={message.id}
|
||||
className={message.isMine ? styles.my : styles.your}
|
||||
>
|
||||
{message.text}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<form
|
||||
action="POST"
|
||||
onSubmit={handleSubmit}
|
||||
className={styles.form}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
/>
|
||||
<button type="submit">
|
||||
<SendIcon />
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
92
frontend/src/components/ChatRoom/ChatRoom.module.css
Normal file
92
frontend/src/components/ChatRoom/ChatRoom.module.css
Normal file
@ -0,0 +1,92 @@
|
||||
.room {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
/* padding: 16px 16px 32px; */
|
||||
padding: 16px;
|
||||
background: linear-gradient(0deg, var(--whiteOpacity50) 0%, var(--background) 50%);
|
||||
/* background-color: var(--whiteOpacity500);
|
||||
backdrop-filter: blur(16px); */
|
||||
}
|
||||
|
||||
.bubbles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
align-items: start;
|
||||
gap: 8px;
|
||||
list-style: none;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.your,
|
||||
.my {
|
||||
display: flex;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--background-secondary);
|
||||
color: var(--text-color);
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.my {
|
||||
align-self: end;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--on-primary);
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
margin: 16px;
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
|
||||
& > input[type='text'] {
|
||||
flex-grow: 1;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--text-color);
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
background-color: var(--background);
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ const instance = axios.create({
|
||||
'Content-type': 'application/json;charset=utf-8',
|
||||
'Access-Control-Allow-Origin': import.meta.env.VITE_ORIGIN,
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
instance.interceptors.request.use((config) => {
|
||||
|
8
frontend/src/utils/chat/chatClient.js
Normal file
8
frontend/src/utils/chat/chatClient.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { Client } from '@stomp/stompjs';
|
||||
|
||||
export const chatClient = new Client({
|
||||
brokerURL: import.meta.env.VITE_CHAT_URL,
|
||||
// TODO: debug 제거
|
||||
debug: (str) => console.log(str),
|
||||
reconnectDelay: 5000,
|
||||
});
|
Loading…
Reference in New Issue
Block a user