From 08f7e525eb77f9086b625417a39f2a2945ee8f42 Mon Sep 17 00:00:00 2001 From: yoonsaeng Date: Thu, 21 Nov 2024 06:51:56 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B1=84=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/SendButton.svg | 4 + src/components/chat/ChatMessage.tsx | 235 +++++++++++------- src/components/mypage/CoachProfileSection.tsx | 6 +- src/constants/apiPath.ts | 2 +- src/constants/assets.tsx | 4 +- src/main.tsx | 13 - 6 files changed, 158 insertions(+), 106 deletions(-) create mode 100644 src/assets/images/SendButton.svg diff --git a/src/assets/images/SendButton.svg b/src/assets/images/SendButton.svg new file mode 100644 index 00000000..a6c7debb --- /dev/null +++ b/src/assets/images/SendButton.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx index feaa6a22..d4455716 100644 --- a/src/components/chat/ChatMessage.tsx +++ b/src/components/chat/ChatMessage.tsx @@ -1,14 +1,14 @@ import { useChatInfo } from "@/store/chat.store"; -import axios from "axios"; -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import { useLocation } from "react-router-dom"; import { styled } from "styled-components"; -import { CompatClient, Stomp } from "@stomp/stompjs"; +import SvgIcon from "../Icon/SvgIcon"; type Message = { content: string; - sender: string; + sender: string | null; // null 허용 }; + const ChatMessage = () => { const location = useLocation(); const queryParams = new URLSearchParams(location.search); @@ -16,76 +16,42 @@ const ChatMessage = () => { const chatRoomId = useChatInfo((state) => state.chatRoomId); const activeHours = useChatInfo((state) => state.activeHours); const isMatching = useChatInfo((state) => state.isMatching); - //웹소켓 연결 객체 - const stompClient = useRef(null); - // 메시지 리스트 - const [messages, setMessages] = useState([]); - // 사용자 입력을 저장할 변수 - const [inputValue, setInputValue] = useState(""); + const [messages, setMessages] = useState([]); // 메시지 상태 + const [inputValue, setInputValue] = useState(""); // 입력 필드 상태 + + const initialMessages = [ + { content: "안녕하세요.", sender: "나" }, + { content: "제가 지금 재활치료를 받고 있는데요", sender: "나" }, + { content: "병행해서 트레이닝 가능할까요?", sender: "나" }, + { content: "반갑습니다, 정회원님😄", sender: nickname }, + { content: "물론입니다!", sender: nickname }, + { + content: + "저는 헬스트레이너 지도사 1급과 함께 운동 처방사 1급 자격증도 보유하고 있어서 안전하고 효율적인 트레이닝이 가능합니다.", + sender: nickname + } + ]; + // 입력 필드 변경 핸들러 const handleInputChange = (event: React.ChangeEvent) => { setInputValue(event.target.value); }; - //메세지 전송 + // 메시지 전송 const sendMessage = () => { - if (stompClient.current && inputValue) { - //현재로서는 임의의 테스트 값을 삽입 - const body = { - id: 1, - name: "테스트1", - message: inputValue + if (inputValue) { + const newMessage: Message = { + content: inputValue, + sender: "나" // 닉네임이 없으면 '익명'으로 설정 }; - stompClient.current.send( - `/pub/chat-rooms/${chatRoomId}`, - {}, - JSON.stringify(body) - ); - setInputValue(""); + setMessages((prevMessages) => [...prevMessages, newMessage]); // 새 메시지 추가 + setInputValue(""); // 입력 필드 초기화 } }; useEffect(() => { - connect(); - fetchMessages(); - // 컴포넌트 언마운트 시 웹소켓 연결 해제 - return () => disconnect(); - }, []); - - const connect = () => { - //웹소켓 연결 - const socket = new WebSocket( - process.env.NODE_ENV === "production" - ? "wss://coach-coach.site/ws" - : "ws://localhost:8080/ws" - ); - stompClient.current = Stomp.over(socket); - stompClient.current.connect({}, () => { - //메시지 수신(1은 roomId를 임시로 표현) - stompClient.current?.subscribe( - `/sub/chat-rooms/${chatRoomId}`, - (message) => { - //누군가 발송했던 메시지를 리스트에 추가 - const newMessage: Message = JSON.parse(message.body); - setMessages((prevMessages) => [...prevMessages, newMessage]); - } - ); - }); - }; - - const fetchMessages = () => { - return axios - .get("/v1/chat-rooms/{chatRoomId}/messages") - .then((response) => { - setMessages(response.data); - }); - }; - - const disconnect = () => { - if (stompClient.current) { - stompClient.current.disconnect(); - } - }; + setMessages(initialMessages); // 기존 메시지 가져오기 + }, [chatRoomId]); const title = (() => { if (activeHours.length > 0 && isMatching) return "코치님과 매칭되었습니다"; @@ -103,35 +69,63 @@ const ChatMessage = () => {
{title}
{activeHours.length > 0 && ( - <> - - {`${nickname}님의 채팅 가능 시간은 `} - {activeHours} - {` 에요`} - - + + {`${nickname}님의 채팅 가능 시간은 `} + {activeHours} + {` 에요`} + )}
- -
    -
    - {/* 입력 필드 */} - - {/* 메시지 전송, 메시지 리스트에 추가 */} - -
    - {/* 메시지 리스트 출력 */} - {messages.map((item, index) => ( -
    - {item.content} -
    - ))} -
+ + {Array.isArray(messages) && + messages.map((item, index) => + item.sender === "나" ? ( + {item.content} + ) : ( + {item.content} + ) + )} + + + + + ); }; -const ChatMessageStyle = styled.div``; +const ChatMessageStyle = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + padding: 10px; + width: 100%; + max-width: 600px; + margin: 0 auto; + position: relative; /* 하단 입력 필드 위치 조정을 위해 relative 추가 */ + height: 100vh; /* 전체 화면 높이 사용 */ +`; + +const ChatContainer = styled.div` + overflow-y: auto; + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + flex-grow: 1; /* 화면의 남은 공간을 채움 */ +`; const ChatTitleStyle = styled.div` position: relative; @@ -139,6 +133,7 @@ const ChatTitleStyle = styled.div` flex-direction: column; align-items: center; padding: 20px 0; + width: 100%; .title { font-size: 20px; @@ -162,8 +157,8 @@ const ChatTitleStyle = styled.div` `; const AbleTimeStyle = styled.div` - word-wrap: break-word; /* 긴 단어 자동 줄바꿈 */ - white-space: pre-wrap; /* 공백 유지 및 줄바꿈 가능 */ + word-wrap: break-word; + white-space: pre-wrap; font-size: 12px; `; @@ -171,4 +166,68 @@ const HighlightedText = styled.span` color: #0075ff; `; +const PartnerChatStyle = styled.div` + align-self: flex-start; /* 부모 컨테이너 기준 왼쪽 정렬 */ + background-color: #0075ff; + color: #ffffff; + border-radius: 20px; + max-width: 350px; + min-height: 40px; + padding: 10px 15px; + margin: 5px 0; + word-wrap: break-word; + white-space: pre-wrap; + display: inline-block; /* 글자 크기에 따라 넓이 조정 */ +`; + +const MyChatStyle = styled.div` + align-self: flex-end; /* 부모 컨테이너 기준 오른쪽 정렬 */ + background-color: #3a3a3a; + color: #ffffff; + border-radius: 20px; + max-width: 350px; + min-height: 40px; + padding: 10px 15px; + margin: 5px 0; + word-wrap: break-word; + white-space: pre-wrap; + display: inline-block; + margin-left: auto; /* 오른쪽 끝으로 이동 */ +`; + +const ChatInputAndButtonStyle = styled.div` + display: flex; + align-items: center; /* 입력 필드와 버튼을 세로 중앙 정렬 */ + position: absolute; + bottom: 150px; /* 화면 하단에서 10px 위로 */ + left: 50%; /* 화면 중앙 정렬을 위해 왼쪽 50% 이동 */ + transform: translateX(-50%); /* 중앙 정렬 보정 */ + width: 100%; /* 부모 컨테이너 너비에 맞춤 */ + max-width: 600px; /* 최대 너비 제한 */ + padding: 10px; + background-color: #1e1e1e; /* 배경색 추가 (선택 사항) */ + border-radius: 10px; /* 테두리 둥글게 */ + + svg { + margin-left: 10px; + } +`; + +const ChatInputStyle = styled.input` + flex-grow: 1; /* 남은 공간을 채움 */ + min-height: 60px; + border-radius: 10px; + background-color: #252932; + padding-left: 20px; + font-size: 18px; + color: #ffffff; + border: none; /* 테두리 제거 */ + outline: none; /* 포커스 시 테두리 제거 */ + + &::placeholder { + color: #777c89; + font-size: 18px; + } +`; + export default ChatMessage; diff --git a/src/components/mypage/CoachProfileSection.tsx b/src/components/mypage/CoachProfileSection.tsx index f3a6a7d7..bacea004 100644 --- a/src/components/mypage/CoachProfileSection.tsx +++ b/src/components/mypage/CoachProfileSection.tsx @@ -34,8 +34,6 @@ const CoachProfileSection = () => { if (userMeData?.isCoach !== undefined) { if (userMeData?.isCoach) { setShouldFetchCoachProfile(true); - } else { - openModal(); } } }, [userMeData, openModal]); @@ -123,7 +121,9 @@ const CoachProfileSection = () => { { + closeModal(); + }} > 확인 diff --git a/src/constants/apiPath.ts b/src/constants/apiPath.ts index 79ff831d..7e2ed65d 100644 --- a/src/constants/apiPath.ts +++ b/src/constants/apiPath.ts @@ -31,5 +31,5 @@ export const API_PATH = { recordV2: "/v2/records", review: "/v1/coaches/reviews", memberChat: "/v1/users/chat-rooms", - coachChat: "v1/coaches/chat-rooms" + coachChat: "/v1/coaches/chat-rooms" }; diff --git a/src/constants/assets.tsx b/src/constants/assets.tsx index 733ffd37..ebf4dff6 100644 --- a/src/constants/assets.tsx +++ b/src/constants/assets.tsx @@ -15,6 +15,7 @@ import Star from "@/assets/images/star.svg?react"; import Warning from "@/assets/images/warning.svg?react"; import XCircle from "@/assets/images/x-circle.svg?react"; import X from "@/assets/images/x.svg?react"; +import SendButton from "@/assets/images/SendButton.svg?react"; import { CgDetailsMore } from "react-icons/cg"; import { FaCheck, FaRegStar } from "react-icons/fa"; import { IoIosArrowBack, IoMdMore } from "react-icons/io"; @@ -70,5 +71,6 @@ export const ICONS = { arrow: ArrowDown, noLocation: NoLocation, warning: Warning, - notification: Notification + notification: Notification, + send: SendButton }; diff --git a/src/main.tsx b/src/main.tsx index 729da718..24ff7cd5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,21 +1,8 @@ import React from "react"; import App from "./App.tsx"; import { HelmetProvider } from "react-helmet-async"; -import * as Sentry from "@sentry/react"; import ReactDOM from "react-dom/client"; -Sentry.init({ - dsn: import.meta.env.VITE_SENTRY_DSN, - integrations: [ - Sentry.browserTracingIntegration(), - Sentry.replayIntegration() - ], - tracesSampleRate: 1.0, - tracePropagationTargets: [import.meta.env.VITE_SENTRY_URL], - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0 -}); - async function enableMocking() { if (import.meta.env.MODE !== "development") { return;