From 14fd2b284408585f5d799e2ca13225a687d38442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=92=E1=85=A7=E1=86=AB=E1=84=87?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Fri, 23 May 2025 22:40:12 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20=EA=B3=BC=EA=B1=B0=20=ED=8C=90?= =?UTF-8?q?=EB=A1=80=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lawmon/src/pages/past/index.tsx | 49 +++++++++++++++++++++++++++++-- lawmon/src/pages/result/index.tsx | 2 +- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lawmon/src/pages/past/index.tsx b/lawmon/src/pages/past/index.tsx index ceb79de..58e2616 100644 --- a/lawmon/src/pages/past/index.tsx +++ b/lawmon/src/pages/past/index.tsx @@ -1,8 +1,51 @@ import React from 'react'; import './index.css'; import good from '../../assets/따봉.svg'; +import { useQuery } from '@tanstack/react-query'; +import { useContractStore } from 'shared/store/store'; export default function Past() { + const titleFromStore = useContractStore(state => state.title); + + // 파일명에서 확장자 제거하는 로직 + let searchQuery = null; + if (titleFromStore && typeof titleFromStore === 'string') { + const lastDotIndex = titleFromStore.lastIndexOf('.'); + let baseName = ''; + if (lastDotIndex > 0) { // '.'이 있고, 파일명의 첫 글자가 아닌 경우 + baseName = titleFromStore.substring(0, lastDotIndex); + } else { + baseName = titleFromStore; // 확장자가 없는 경우 그대로 사용 + } + // 문자열을 NFC 형식으로 정규화 + searchQuery = baseName.normalize('NFC'); + } + + const { isLoading, error, data } = useQuery({ + queryKey: ['pasts', searchQuery], // 가공된 searchQuery를 queryKey로 사용 + queryFn: async () => { + if (!searchQuery) { // searchQuery가 없으면 요청하지 않음 + return Promise.resolve(null); // 또는 적절한 기본값 반환 + } + // 이제 searchQuery는 NFC 형식이므로, Swagger와 동일한 인코딩 결과가 나올 것입니다. + const res = await fetch( + `${import.meta.env.VITE_API_URL}/api/v1/law/search?query=${encodeURIComponent(searchQuery)}` + ); + if (!res.ok) { + throw new Error('Network response was not ok'); + } + return res.json(); + }, + enabled: !!searchQuery, // 가공된 searchQuery가 있을 때만 쿼리 실행 + }); + + console.log('Original title from store: ', titleFromStore); // 원본 제목 + console.log('Search query: ', searchQuery); // 가공된 검색어 + console.log("Data: ", data); + + // const pastss = data || []; // API 응답 데이터를 사용하거나, 없을 경우 빈 배열 사용 + + const pasts = [ { description: '계약 조건의 불명확성', @@ -45,7 +88,7 @@ export default function Past() { 과거에 비슷한 고민이 있었어요!
- {pasts.map((past, index) => ( + {data && data.map((past: any, index: number) => (
{past.description} -

{past.company1}

-

{past.company2}

+

{past["사건번호"]}

+

{past["사건명"]}

{/* 하단 버튼 컨테이너 */} diff --git a/lawmon/src/pages/result/index.tsx b/lawmon/src/pages/result/index.tsx index 0f76e32..bb00da7 100644 --- a/lawmon/src/pages/result/index.tsx +++ b/lawmon/src/pages/result/index.tsx @@ -124,7 +124,7 @@ export default function Result() { -
From a8c5dadfea941de64351047b6c35be4732d71eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=92=E1=85=A7=E1=86=AB=E1=84=87?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Sat, 31 May 2025 11:41:07 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=20feat=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C.=20/=20mock=20data=20=EB=84=A3=EC=96=B4?= =?UTF-8?q?=EB=86=A8=EC=9C=BC=EB=8B=88=20=EB=82=98=EC=A4=91=EC=97=90=20?= =?UTF-8?q?=EC=A7=80=EC=9A=B8=EA=B2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lawmon/src/pages/expert/index.tsx | 93 ++++++++++++++++++++++++++----- lawmon/src/shared/store/store.ts | 4 ++ 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/lawmon/src/pages/expert/index.tsx b/lawmon/src/pages/expert/index.tsx index 591ff48..202c8af 100644 --- a/lawmon/src/pages/expert/index.tsx +++ b/lawmon/src/pages/expert/index.tsx @@ -3,17 +3,25 @@ import './index.css'; import expertImage from '/src/assets/윾건이형.png'; // 전문가 프로필 이미지 예제 import smallStar from '/src/assets/작은별.svg'; // 보라색 채운 별 이미지 경로 import talk from '../../assets/상담하기.svg'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useMutation } from '@tanstack/react-query'; import { useContractStore } from 'shared/store/store'; import Loading from './Loading'; +import { useNavigate } from 'react-router-dom'; // 라우터 네비게이션을 위해 추가 export default function Expert() { + const navigate = useNavigate(); +// const category = useContractStore((state) => state.category); // 예시 수행 후 풀기 + const category = "REAL_ESTATE"; // 예시로 고정된 카테고리, + // memberId를 store에서 가져오거나 localStorage에서 가져옵니다 + const memberId = useContractStore((state) => state.memberId); // store에 memberId가 있다고 가정 - const category = useContractStore((state) => state.category); -// const setCategory = useContractStore((state) => state.setCategory); // 지우기 -// useEffect(() => { //지우기 -// setCategory('REAL_ESTATE'); // 지우기 -// }, []); + const setMemberId = useContractStore((state) => state.setMemberId); // store에서 memberId 설정 함수 예시 수행 후 삭제 + + useEffect(() => { + setMemberId?.(1); // 예시로 1번 멤버 ID 설정 + + }, [setMemberId]); + const { isPending, error, data } = useQuery({ queryKey: ['experts', category], @@ -28,12 +36,13 @@ export default function Expert() { }, enabled: !!category, // category가 있을 때만 쿼리 실행 }); -// console.log('category : ', category); //지우기 -// console.log('data :', data); //지우기 + console.log('category : ', category); //지우기 + console.log('data :', data); //지우기 interface ExpertApiResponse { name: string; specialty: string; + id: number; // 필요한 경우 rating, reviews, education, company1, company2 등 추가 } @@ -49,9 +58,56 @@ export default function Expert() { company1: '', // 임시값 company2: '', // 임시값 image: expertImage, + id: item.id, // 전문가 ID 추가 })) : []; + // 채팅방 생성 API 호출을 위한 mutation + const createChatRoomMutation = useMutation({ + mutationFn: async ({ expertId, memberId }: { expertId: number; memberId: number }) => { + const response = await fetch( + `${import.meta.env.VITE_API_URL}/api/v1/chat/rooms/expert?expertId=${expertId}&memberId=${memberId}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + if (!response.ok) { + throw new Error('채팅방 생성에 실패했습니다.'); + } + + return response.json(); + }, + onSuccess: (data) => { + // 채팅방 생성 성공 시 채팅방으로 이동 + console.log('채팅방 생성 성공:', data); + // 채팅방 페이지로 이동 (라우트는 프로젝트 구조에 맞게 수정하세요) + navigate(`/chat/${data.name}`, { + state: { + roomId: data.roomId, + roomName: data.name + } + }); + }, + onError: (error) => { + console.error('채팅방 생성 실패:', error); + alert('채팅방 생성에 실패했습니다. 다시 시도해주세요.'); + }, + }); + + // 상담하기 버튼 클릭 핸들러 + const handleStartConsultation = (expertId: number) => { + if (!memberId) { + alert('로그인이 필요합니다.'); + return; + } + + createChatRoomMutation.mutate({ expertId, memberId }); + }; + return (

@@ -68,13 +124,10 @@ export default function Expert() { >
- {' '} - {/* 텍스트가 왼쪽에 배치될 공간을 확보 */}

{expert.name}

- {/* 별을 이미지로 채우기 */} {[...Array(expert.rating)].map((_, starIndex) => (

- {/* 왼쪽: 경력 정보 */}

"{expert.description}"

    @@ -108,9 +160,20 @@ export default function Expert() {
- {/* 오른쪽: 챗 버튼 */} -
diff --git a/lawmon/src/shared/store/store.ts b/lawmon/src/shared/store/store.ts index 1f67a72..af2d2ca 100644 --- a/lawmon/src/shared/store/store.ts +++ b/lawmon/src/shared/store/store.ts @@ -11,6 +11,8 @@ interface ContractState { setCategory: (category: string | null) => void; ContractURL: string | null; setContractURL: (url: string | null) => void; + memberId?: number; // 선택적 속성으로 memberId 추가 + setMemberId?: (id: number) => void; // memberId를 설정하는 함수 } export const useContractStore = create((set) => ({ @@ -24,4 +26,6 @@ export const useContractStore = create((set) => ({ setSelectedFile: (file) => set({ selectedFile: file }), ContractURL: null, setContractURL: (url) => set({ ContractURL: url }), + memberId: undefined, + setMemberId: (id) => set({ memberId: id }), })); From 12d0deda0c66bb8568e02fb2bc14ceb3a5913011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=92=E1=85=A7=E1=86=AB=E1=84=87?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Tue, 17 Jun 2025 11:46:38 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat=20:=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=97=B0=EA=B1=B8=20=EB=B0=8F=20=EC=97=B0=EA=B2=B0=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lawmon/package-lock.json | 385 ++++++++++++++++++++++++++++- lawmon/package.json | 4 + lawmon/src/pages/chat/ChatRoom.css | 17 ++ lawmon/src/pages/chat/ChatRoom.tsx | 232 +++++++++++------ lawmon/vite.config.ts | 3 + lawmon/yarn.lock | 231 +++++++++++++++++ 6 files changed, 802 insertions(+), 70 deletions(-) diff --git a/lawmon/package-lock.json b/lawmon/package-lock.json index bfe61e4..daa7a3d 100644 --- a/lawmon/package-lock.json +++ b/lawmon/package-lock.json @@ -17,12 +17,16 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.2.0", + "sockjs-client": "^1.6.1", + "stompjs": "^2.3.3", "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.13.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@types/sockjs-client": "^1.5.4", + "@types/stompjs": "^2.3.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitejs/plugin-react": "^4.3.3", @@ -1782,6 +1786,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -1810,6 +1824,23 @@ "@types/react": "*" } }, + "node_modules/@types/sockjs-client": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.4.tgz", + "integrity": "sha512-zk+uFZeWyvJ5ZFkLIwoGA/DfJ+pYzcZ8eH4H/EILCm2OBZyHH6Hkdna1/UWL/CFruh5wj6ES7g75SvUB0VsH5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stompjs": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/stompjs/-/stompjs-2.3.9.tgz", + "integrity": "sha512-fu/GgkRdxwyEJ+JeUsGhDxGwmZQi+xeNElradGQ4ehWiG2z/o89gsi5Y7Gv0KC6VK1v78Cjh8zj3VF+RvqCGSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", @@ -2439,6 +2470,20 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -2643,6 +2688,20 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "optional": true, + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", @@ -2995,6 +3054,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "optional": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "optional": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -3463,6 +3565,22 @@ "node": "*" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "optional": true, + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -3540,6 +3658,36 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "optional": true, + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3608,6 +3756,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4019,6 +4179,12 @@ "node": ">= 0.4" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4056,6 +4222,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -4416,6 +4588,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT", + "optional": true + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -5000,7 +5179,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -5040,6 +5218,25 @@ "dev": true, "license": "MIT" }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC", + "optional": true + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -5588,6 +5785,12 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5765,6 +5968,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5884,6 +6093,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -6025,6 +6254,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6034,6 +6291,15 @@ "node": ">=0.10.0" } }, + "node_modules/stompjs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/stompjs/-/stompjs-2.3.3.tgz", + "integrity": "sha512-5l/Ogz0DTFW7TrpHF0LAETGqM/so8UxNJvYZjJKqcX31EVprSQgnGkO80tZctPC/lFBDUrSFiTG3xd0R27XAIA==", + "license": "Apache-2.0", + "optionalDependencies": { + "websocket": "latest" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -6464,6 +6730,13 @@ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", "license": "ISC" }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6554,6 +6827,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -6611,6 +6894,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", @@ -6652,6 +6942,30 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6718,6 +7032,64 @@ } } }, + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/websocket/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6922,6 +7294,17 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.32" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/lawmon/package.json b/lawmon/package.json index 3df41c4..d43c564 100644 --- a/lawmon/package.json +++ b/lawmon/package.json @@ -19,12 +19,16 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.2.0", + "sockjs-client": "^1.6.1", + "stompjs": "^2.3.3", "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.13.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@types/sockjs-client": "^1.5.4", + "@types/stompjs": "^2.3.9", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitejs/plugin-react": "^4.3.3", diff --git a/lawmon/src/pages/chat/ChatRoom.css b/lawmon/src/pages/chat/ChatRoom.css index fcae2ea..dbf5bad 100644 --- a/lawmon/src/pages/chat/ChatRoom.css +++ b/lawmon/src/pages/chat/ChatRoom.css @@ -50,3 +50,20 @@ cursor: pointer; transition: background-color 0.3s; } +.connection-status { + font-size: 12px; + padding: 4px 8px; + border-radius: 12px; + background-color: #f3f4f6; + font-weight: 500; +} + +.connection-status.connected { + color: #059669; + background-color: #d1fae5; +} + +.connection-status.disconnected { + color: #dc2626; + background-color: #fee2e2; +} diff --git a/lawmon/src/pages/chat/ChatRoom.tsx b/lawmon/src/pages/chat/ChatRoom.tsx index 449e3d8..fe9c8b0 100644 --- a/lawmon/src/pages/chat/ChatRoom.tsx +++ b/lawmon/src/pages/chat/ChatRoom.tsx @@ -3,7 +3,19 @@ import './ChatRoom.css'; import ChattingComponent from './ChattingComponent'; import UserImage from '/src/assets/윾건이형.png'; import send from '../../assets/로우몬제출이모티콘.svg'; -import { useParams } from 'react-router-dom'; +import { useParams, useLocation } from 'react-router-dom'; +import SockJS from 'sockjs-client'; +import Stomp, { Client } from 'stompjs'; +import { useContractStore } from 'shared/store/store'; + +// 메시지 인터페이스 정의 +interface ChatMessage { + type: 'ENTER' | 'TALK' | 'LEAVE'; + roomId: string; + sender: string; + message: string; + timestamp: string; +} //Chatting 인터페이스 정의 interface Chatting { @@ -16,47 +28,135 @@ interface Chatting { } function Chat() { + const { roomName } = useParams(); + const location = useLocation(); + const { roomId } = location.state || {}; + + // Zustand store에서 memberId 가져오기 + const memberId = useContractStore((state) => state.memberId); + + // WebSocket 관련 상태 + const [ws, setWs] = useState(null); + const [isConnected, setIsConnected] = useState(false); + const [reconnectCount, setReconnectCount] = useState(0); + //chattings 상태 변수와 setChattings 함수 정의 - const [chattings, setChattings] = useState([ - { - id: 1, - nickname: '양준석(팀장)', - profileImage: UserImage, - chatting: `안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. 안녕하세요 프론트엔드 팀원 여러분,. `, - time: '17:06', - isMe: false, - }, - { - id: 2, - nickname: '아무개', - profileImage: UserImage, - chatting: `신규 개발 중인 개인정보 수정 탭의 사이드 탭의 UI 개발 을 맡고 - 있는 해당 팀원 분들은 저에게 진척 사항 공유 부탁드립니다~.`, - time: '17:07', - isMe: true, - }, - { - id: 3, - nickname: '김민수', - profileImage: UserImage, - chatting: `저랑 이현빈 팀원이 개발 중에 있습니다! 진척 상황 노션에 - 정리하여 곧 공유드리겠습니다!`, - time: '17:08', - isMe: false, - }, - { - id: 4, - nickname: '아무게', - profileImage: UserImage, - chatting: `신규 개발 중인 개인정보 수정 탭의 사이드 탭의 UI 개발 을 맡고 - 있는 해당 팀원 분들은 저에게 진척 사항 공유 부탁드립니다~ `, - time: '17:09', - isMe: true, - }, - ]); + const [chattings, setChattings] = useState([]); + /* textarea 값 input으로 정의 */ const [input, setInput] = useState(''); const chatEndRef = useRef(null); + + // WebSocket 연결 함수 - 타입 오류 수정 + const connect = () => { + if (!roomId || !memberId) { + console.log('Missing roomId or memberId', { roomId, memberId }); + return; + } + + console.log('Attempting to connect to WebSocket...'); + const sock = new SockJS('http://localhost:8080/ws-stomp'); + const stompClient = Stomp.over(sock); + + // 타입 오류 해결: any로 캐스팅하거나 정확한 타입 사용 + stompClient.connect( + {}, // 빈 헤더 객체 + (frame?: any) => { // frame을 optional로 처리 + console.log('Connected: ', frame); + setIsConnected(true); + setReconnectCount(0); // 연결 성공 시 재연결 카운트 리셋 + + // 채팅방 구독 + stompClient.subscribe(`/sub/chat/room/${roomId}`, (message: any) => { + const recv: ChatMessage = JSON.parse(message.body); + + // 받은 메시지를 Chatting 형태로 변환 + const newChatting: Chatting = { + id: Date.now(), + nickname: recv.sender, + profileImage: UserImage, + chatting: recv.message, + time: new Date(recv.timestamp).toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit' + }), + isMe: recv.sender === String(memberId) + }; + + // ENTER, LEAVE 메시지는 시스템 메시지로 처리 + if (recv.type === 'ENTER' || recv.type === 'LEAVE') { + if (recv.message) { + setChattings(prev => [...prev, { + ...newChatting, + nickname: '[알림]', + isMe: false + }]); + } + } else if (recv.type === 'TALK') { + setChattings(prev => [...prev, newChatting]); + } + }); + + // 입장 메시지 전송 + stompClient.send("/pub/chat/message", {}, JSON.stringify({ + type: 'ENTER', + roomId: roomId, + sender: String(memberId), + message: `${memberId}님이 입장했습니다.`, + timestamp: new Date().toISOString() + })); + + setWs(stompClient); + }, + (error?: any) => { // error도 optional로 처리 + console.error('WebSocket connection error:', error); + setIsConnected(false); + + // 재연결 횟수 제한 (최대 3회) + if (reconnectCount < 3) { + setTimeout(() => { + console.log(`Attempting to reconnect... (${reconnectCount + 1}/3)`); + setReconnectCount(prev => prev + 1); + connect(); + }, 5000); + } else { + console.log('Max reconnection attempts reached. Please check backend server.'); + } + } + ); + }; + + // 컴포넌트 마운트 시 WebSocket 연결 + useEffect(() => { + connect(); + + // 컴포넌트 언마운트 시 연결 해제 + return () => { + if (ws && isConnected) { + // 퇴장 메시지 전송 + try { + ws.send("/pub/chat/message", {}, JSON.stringify({ + type: 'LEAVE', + roomId: roomId, + sender: String(memberId), + message: `${memberId}님이 퇴장했습니다.`, + timestamp: new Date().toISOString() + })); + + // disconnect 호출 시 타입 오류 방지 + if (ws && typeof ws.disconnect === 'function') { + ws.disconnect(() => { + console.log('Disconnected'); + }); + } + } catch (error) { + console.error('Error during disconnect:', error); + } + } + }; + }, [roomId, memberId]); + + // 채팅 자동 스크롤 useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [chattings]); @@ -71,44 +171,34 @@ function Chat() { handleSendMessage(); } }; - // const handleKeyUp = (e: React.KeyboardEvent) => { - // if (e.key === 'Enter' && !e.shiftKey) { - // e.preventDefault(); - // handleSendMessage(); - // } - // }; /* sendMessage 함수*/ const handleSendMessage = () => { - if (input.trim()) { - const currentInput = input; + if (input.trim() && ws && isConnected) { + const messageData: ChatMessage = { + type: 'TALK', + roomId: roomId, + sender: String(memberId), + message: input.trim(), + timestamp: new Date().toISOString() + }; + + // WebSocket으로 메시지 전송 + ws.send("/pub/chat/message", {}, JSON.stringify(messageData)); + setInput(''); - setChattings((prevMessages) => { - //이전 메세지 배열이 비어 있는지 확인하고, 비어 있지 않으면 마지막 메세지의 id를 가져옴 - const newId = - prevMessages.length > 0 - ? prevMessages[prevMessages.length - 1].id + 1 - : 1; - return [ - ...prevMessages, - { - id: newId, - nickname: '아무게', - profileImage: 'me', - chatting: currentInput, - time: new Date().toLocaleTimeString(), - isMe: true, - }, - ]; - }); - // console.log(chattings); } }; return ( <>
-

LAWMON

+

+ LAWMON - {roomName} +

+
+ {isConnected ? '🟢 연결됨' : '🔴 연결 끊김'} +
setInput(e.target.value)} + disabled={!isConnected} /> -
diff --git a/lawmon/vite.config.ts b/lawmon/vite.config.ts index e9300f1..35369ee 100644 --- a/lawmon/vite.config.ts +++ b/lawmon/vite.config.ts @@ -19,4 +19,7 @@ export default defineConfig({ shared: path.resolve(__dirname, './src/shared'), }, }, + define: { + global: 'globalThis', + }, }); diff --git a/lawmon/yarn.lock b/lawmon/yarn.lock index 805f16c..502a606 100644 --- a/lawmon/yarn.lock +++ b/lawmon/yarn.lock @@ -481,6 +481,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/node@*", "@types/node@^18.0.0 || >=20.0.0": + version "24.0.3" + resolved "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz" + integrity sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg== + dependencies: + undici-types "~7.8.0" + "@types/prop-types@*": version "15.7.13" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz" @@ -501,6 +508,18 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/sockjs-client@^1.5.4": + version "1.5.4" + resolved "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.4.tgz" + integrity sha512-zk+uFZeWyvJ5ZFkLIwoGA/DfJ+pYzcZ8eH4H/EILCm2OBZyHH6Hkdna1/UWL/CFruh5wj6ES7g75SvUB0VsH5w== + +"@types/stompjs@^2.3.9": + version "2.3.9" + resolved "https://registry.npmjs.org/@types/stompjs/-/stompjs-2.3.9.tgz" + integrity sha512-fu/GgkRdxwyEJ+JeUsGhDxGwmZQi+xeNElradGQ4ehWiG2z/o89gsi5Y7Gv0KC6VK1v78Cjh8zj3VF+RvqCGSA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^8.15.0", "@typescript-eslint/eslint-plugin@8.15.0": version "8.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz" @@ -813,6 +832,13 @@ browserslist@^4.23.3, browserslist@^4.24.0, "browserslist@>= 4.21.0": node-releases "^2.0.18" update-browserslist-db "^1.1.1" +bufferutil@^4.0.1: + version "4.0.9" + resolved "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz" + integrity sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw== + dependencies: + node-gyp-build "^4.3.0" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -913,6 +939,14 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +d@^1.0.1, d@^1.0.2, d@1: + version "1.0.2" + resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz" + integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== + dependencies: + es5-ext "^0.10.64" + type "^2.7.2" + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz" @@ -940,6 +974,13 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -1144,6 +1185,33 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14: + version "0.10.64" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.4" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz" + integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== + dependencies: + d "^1.0.2" + ext "^1.7.0" + esbuild@^0.21.3: version "0.21.5" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" @@ -1329,6 +1397,16 @@ eslint-visitor-keys@^4.2.0: natural-compare "^1.4.0" optionator "^0.9.3" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^10.0.1, espree@^10.3.0: version "10.3.0" resolved "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz" @@ -1362,6 +1440,26 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== + +ext@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -1400,6 +1498,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@^0.11.4: + version "0.11.4" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" @@ -1628,6 +1733,11 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +http-parser-js@>=0.5.1: + version "0.5.10" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" @@ -1646,6 +1756,11 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz" @@ -1812,6 +1927,11 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz" @@ -2064,6 +2184,11 @@ ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + mz@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" @@ -2083,6 +2208,16 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-gyp-build@^4.3.0: + version "4.8.4" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + node-releases@^2.0.18: version "2.0.18" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" @@ -2340,6 +2475,11 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -2429,6 +2569,11 @@ regexp.prototype.flags@^1.5.2, regexp.prototype.flags@^1.5.3: es-errors "^1.3.0" set-function-name "^2.0.2" +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" @@ -2501,6 +2646,11 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-buffer@>=5.1.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz" @@ -2586,11 +2736,29 @@ signal-exit@^4.0.1: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +sockjs-client@^1.6.1: + version "1.6.1" + resolved "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz" + integrity sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw== + dependencies: + debug "^3.2.7" + eventsource "^2.0.2" + faye-websocket "^0.11.4" + inherits "^2.0.4" + url-parse "^1.5.10" + source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +stompjs@^2.3.3: + version "2.3.3" + resolved "https://registry.npmjs.org/stompjs/-/stompjs-2.3.3.tgz" + integrity sha512-5l/Ogz0DTFW7TrpHF0LAETGqM/so8UxNJvYZjJKqcX31EVprSQgnGkO80tZctPC/lFBDUrSFiTG3xd0R27XAIA== + optionalDependencies: + websocket latest + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -2837,6 +3005,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type@^2.7.2: + version "2.7.3" + resolved "https://registry.npmjs.org/type/-/type-2.7.3.tgz" + integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz" @@ -2881,6 +3054,13 @@ typed-array-length@^1.0.6: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript-eslint@^8.11.0: version "8.15.0" resolved "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.15.0.tgz" @@ -2905,6 +3085,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~7.8.0: + version "7.8.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz" + integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== + update-browserslist-db@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz" @@ -2920,6 +3105,21 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse@^1.5.10: + version "1.5.10" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" @@ -2936,6 +3136,32 @@ util-deprecate@^1.0.2: optionalDependencies: fsevents "~2.3.3" +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +websocket@latest: + version "1.0.35" + resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz" + integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.63" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" @@ -3016,6 +3242,11 @@ wrap-ansi@^8.1.0: string-width "^5.0.1" strip-ansi "^7.0.1" +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" From 9b18c0555cd6e2dfada584c933849bf37f35536e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=92=E1=85=A7=E1=86=AB=E1=84=87?= =?UTF-8?q?=E1=85=B5=E1=86=AB?= Date: Tue, 17 Jun 2025 20:16:54 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=20feat=20:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=86=8C=EC=BC=93=20=EC=97=B0=EA=B2=B0=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lawmon/src/pages/chat/ChatRoom.tsx | 84 +++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/lawmon/src/pages/chat/ChatRoom.tsx b/lawmon/src/pages/chat/ChatRoom.tsx index fe9c8b0..decad35 100644 --- a/lawmon/src/pages/chat/ChatRoom.tsx +++ b/lawmon/src/pages/chat/ChatRoom.tsx @@ -67,33 +67,49 @@ function Chat() { setReconnectCount(0); // 연결 성공 시 재연결 카운트 리셋 // 채팅방 구독 - stompClient.subscribe(`/sub/chat/room/${roomId}`, (message: any) => { - const recv: ChatMessage = JSON.parse(message.body); + stompClient.subscribe(`/sub/chat/room/${roomId}`, (message) => { + console.log('>>> RECEIVED MESSAGE:', message); + console.log('>>> MESSAGE BODY:', message.body); - // 받은 메시지를 Chatting 형태로 변환 - const newChatting: Chatting = { - id: Date.now(), - nickname: recv.sender, - profileImage: UserImage, - chatting: recv.message, - time: new Date(recv.timestamp).toLocaleTimeString('ko-KR', { - hour: '2-digit', - minute: '2-digit' - }), - isMe: recv.sender === String(memberId) - }; - - // ENTER, LEAVE 메시지는 시스템 메시지로 처리 - if (recv.type === 'ENTER' || recv.type === 'LEAVE') { - if (recv.message) { - setChattings(prev => [...prev, { - ...newChatting, - nickname: '[알림]', - isMe: false - }]); + try { + const recv: ChatMessage = JSON.parse(message.body); + console.log('>>> PARSED MESSAGE:', recv); + + // 받은 메시지를 Chatting 형태로 변환 + const newChatting: Chatting = { + id: Date.now(), + nickname: recv.sender, + profileImage: UserImage, + chatting: recv.message, + time: new Date(recv.timestamp).toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit' + }), + isMe: recv.sender === String(memberId) + }; + + // ENTER, LEAVE 메시지는 시스템 메시지로 처리 + if (recv.type === 'ENTER' || recv.type === 'LEAVE') { + if (recv.message) { + setChattings(prev => { + const updated = [...prev, { + ...newChatting, + nickname: '[알림]', + isMe: false + }]; + console.log('>>> UPDATED CHATTINGS (SYSTEM):', updated); + return updated; + }); + } + } else if (recv.type === 'TALK') { + setChattings(prev => { + const updated = [...prev, newChatting]; + console.log('>>> UPDATED CHATTINGS (TALK):', updated); + return updated; + }); } - } else if (recv.type === 'TALK') { - setChattings(prev => [...prev, newChatting]); + } catch (error) { + console.error('>>> MESSAGE PARSE ERROR:', error); } }); @@ -178,14 +194,30 @@ function Chat() { const messageData: ChatMessage = { type: 'TALK', roomId: roomId, - sender: String(memberId), + sender: String(memberId), // number를 string으로 변환 message: input.trim(), timestamp: new Date().toISOString() }; + console.log('>>> SENDING MESSAGE:', messageData); + // WebSocket으로 메시지 전송 ws.send("/pub/chat/message", {}, JSON.stringify(messageData)); + // 임시: 내가 보낸 메시지 즉시 화면에 추가 (백엔드 문제 해결 전까지) + const newChatting: Chatting = { + id: Date.now(), + nickname: String(memberId), + profileImage: UserImage, + chatting: input.trim(), + time: new Date().toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit' + }), + isMe: true + }; + + setChattings(prev => [...prev, newChatting]); setInput(''); } };