Skip to content

Commit

Permalink
Feature/#105 - 채팅 웹소켓 연결 (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
baegyeong authored Nov 21, 2024
2 parents 5956055 + 356187f commit b15ddde
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
"socket.io-client": "^4.8.1",
"tailwind-merge": "^2.5.4"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/components/layouts/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Sidebar = () => {
<div ref={ref}>
<nav
className={cn(
'fixed left-0 top-0 h-full cursor-pointer bg-white px-1 py-4 shadow-md',
'fixed left-0 top-0 z-10 h-full cursor-pointer bg-white px-1 py-4 shadow-md',
'transition-all duration-300 ease-in-out',
isHovered ? 'w-60' : 'w-24',
)}
Expand Down
52 changes: 50 additions & 2 deletions packages/frontend/src/pages/stock-detail/ChatPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,64 @@
import type { ChatDataType, ChatDataResponse } from '@/sockets/types';
import { useEffect, useState } from 'react';
import { TextArea } from './components';
import { ChatMessage } from './components/ChatMessage';
import DownArrow from '@/assets/down-arrow.svg?react';
import { useSocketChat } from '@/sockets/chat';

export const ChatPanel = () => {
const STOCK_ID = '005930';

const [chatData, setChatData] = useState<ChatDataType[]>();
const { socket: socketChat, isConnected } = useSocketChat({
stockId: STOCK_ID,
});

const handleSendMessage = (message: string) => {
if (isConnected) {
socketChat.emit('chat', {
room: STOCK_ID,
content: message,
});
}
};

useEffect(() => {
const handleChat = (message: ChatDataResponse) => {
console.log('Received message:', message);
if (message?.chats) {
console.log('Chats:', message.chats);
setChatData(message.chats);
}
};

if (isConnected) {
socketChat.on('chat', handleChat);

return () => {
socketChat.off('chat', handleChat);
};
}
}, [socketChat, isConnected]);

return (
<article className="flex flex-col gap-5 rounded-md bg-white p-7">
<h2 className="display-medium20 text-center">채팅</h2>
<TextArea />
<TextArea onSend={handleSendMessage} />
<div className="border-light-gray flex items-center justify-end gap-1 border-b-2 pb-2">
<p className="display-medium12 text-dark-gray">최신순</p>
<DownArrow className="cursor-pointer" />
</div>
<section className="flex flex-col gap-5"></section>
<section className="flex flex-col gap-5">
{chatData?.map((chat) => (
<ChatMessage
key={chat.id}
name={chat.nickname}
contents={chat.message}
likeCount={chat.likeCount}
liked={chat.liked}
/>
))}
</section>
</article>
);
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import Like from '@/assets/like.svg?react';
import { cn } from '@/utils/cn';

interface ChatMessageProps {
name: string;
contents: string;
like: number;
likeCount: number;
liked: boolean;
}

export const ChatMessage = ({ name, contents, like }: ChatMessageProps) => {
export const ChatMessage = ({
name,
contents,
likeCount,
liked,
}: ChatMessageProps) => {
return (
<div className="flex">
<p className="display-bold12 text-dark-gray mr-3 w-12 flex-shrink-0">
Expand All @@ -15,8 +22,13 @@ export const ChatMessage = ({ name, contents, like }: ChatMessageProps) => {
<div>
<p className="display-medium12 text-dark-gray">{contents}</p>
<div className="flex items-center gap-1">
<Like className="fill-gray hover:fill-orange cursor-pointer" />
<span className="display-medium12 text-gray">{like}</span>
<Like
className={
(cn('hover:fill-orange cursor-pointer'),
liked ? 'fill-orange' : 'fill-gray')
}
/>
<span className="display-medium12 text-gray">{likeCount}</span>
</div>
</div>
</div>
Expand Down
23 changes: 19 additions & 4 deletions packages/frontend/src/pages/stock-detail/components/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { type FormEvent, useState } from 'react';
import Send from '@/assets/send.svg?react';

export const TextArea = () => {
interface TextAreaProps {
onSend: (text: string) => void;
}

export const TextArea = ({ onSend }: TextAreaProps) => {
const [chatText, setChatText] = useState('');

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
onSend(chatText);
setChatText('');
};

return (
<div className="relative w-full">
<form className="relative w-full" onSubmit={(e) => handleSubmit(e)}>
<textarea
maxLength={100}
value={chatText}
placeholder="주주 사용자만 입력 가능해요."
className="display-medium12 bg-light-yellow h-20 w-full resize-none rounded-md p-3 pr-10 focus:outline-none"
></textarea>
onChange={(e) => setChatText(e.target.value)}
/>
<button className="bg-orange absolute right-2 top-1/2 -translate-y-1/2 rounded p-1">
<Send />
</button>
</div>
</form>
);
};
30 changes: 30 additions & 0 deletions packages/frontend/src/sockets/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useMemo, useState } from 'react';
import { socketChat, type SocketChatType } from './config';

export const useSocketChat = ({ stockId, pageSize }: SocketChatType) => {
const socket = useMemo(() => {
return socketChat({ stockId, pageSize });
}, [pageSize, stockId]);

const [isConnected, setIsConnected] = useState(socket.connected);

useEffect(() => {
const onConnect = () => {
setIsConnected(true);
};

const onDisconnect = () => {
setIsConnected(false);
};

socket.on('connect', onConnect);
socket.on('disconnect', onDisconnect);

return () => {
socket.off('connect', onConnect);
socket.off('disconnect', onDisconnect);
};
}, [socket]);

return { socket, isConnected };
};
24 changes: 24 additions & 0 deletions packages/frontend/src/sockets/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { io } from 'socket.io-client';

const URL = 'ws://juchum.info';

export interface SocketChatType {
stockId: string;
pageSize?: number;
}

export const socketChat = ({ stockId, pageSize = 20 }: SocketChatType) => {
return io(`${URL}/api/chat/realtime`, {
transports: ['websocket'],
reconnectionDelayMax: 10000,
query: {
stockId,
pageSize,
},
});
};

export const socketStock = io(`${URL}/api/stock/realtime`, {
transports: ['websocket'],
reconnectionDelayMax: 10000,
});
14 changes: 14 additions & 0 deletions packages/frontend/src/sockets/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface ChatDataType {
id: number;
likeCount: number;
message: string;
type: string;
createdAt: Date;
liked: boolean;
nickname: string;
}

export interface ChatDataResponse {
chats?: ChatDataType[];
hasMore: boolean;
}
51 changes: 50 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,11 @@
dependencies:
"@sinonjs/commons" "^3.0.0"

"@socket.io/component-emitter@~3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==

"@storybook/[email protected]":
version "8.4.3"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-8.4.3.tgz#1645b5bd5e11cab64debf4e113238a4b84212f64"
Expand Down Expand Up @@ -3485,7 +3490,7 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"

debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
Expand Down Expand Up @@ -3701,6 +3706,22 @@ encodeurl@~2.0.0:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==

engine.io-client@~6.6.1:
version "6.6.2"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.2.tgz#e0a09e1c90effe5d6264da1c56d7281998f1e50b"
integrity sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
engine.io-parser "~5.2.1"
ws "~8.17.1"
xmlhttprequest-ssl "~2.1.1"

engine.io-parser@~5.2.1:
version "5.2.3"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f"
integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==

enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0:
version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
Expand Down Expand Up @@ -7242,6 +7263,24 @@ snake-case@^3.0.4:
dot-case "^3.0.4"
tslib "^2.0.3"

socket.io-client@^4.8.1:
version "4.8.1"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb"
integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.2"
engine.io-client "~6.6.1"
socket.io-parser "~4.2.4"

socket.io-parser@~4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"

source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
Expand Down Expand Up @@ -8217,6 +8256,16 @@ ws@^8.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==

ws@~8.17.1:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b"
integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==

xmlhttprequest-ssl@~2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23"
integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==

xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
Expand Down

0 comments on commit b15ddde

Please sign in to comment.