Skip to content

Commit 3440b17

Browse files
authored
Merge pull request #149 from Soohyuniii/feat/채팅-UI-개발
feat: chat room UI 개발
2 parents b58a146 + 973dfa7 commit 3440b17

File tree

7 files changed

+170
-41
lines changed

7 files changed

+170
-41
lines changed

src/components/ChatAction.tsx

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/components/ChatActionBar.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ChangeEvent, KeyboardEvent } from "react";
2+
import ToggleChatTipsButton from "@/components/button/ToggleChatTipsButton";
3+
import MessageInput from "@/components/input/MessageInput";
4+
5+
interface ChatActionProps {
6+
isOpen: boolean;
7+
setIsOpen: (open: boolean) => void;
8+
value: string;
9+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
10+
onKeyUp: (e: KeyboardEvent<HTMLInputElement>) => void;
11+
onSend: () => void;
12+
}
13+
14+
const ChatActionBar = ({
15+
isOpen,
16+
setIsOpen,
17+
value,
18+
onChange,
19+
onKeyUp,
20+
onSend
21+
}: ChatActionProps) => {
22+
return (
23+
<div className="flex h-[68px] w-full items-center justify-center border-t border-gray-100 bg-white">
24+
<ToggleChatTipsButton isOpen={isOpen} setIsOpen={setIsOpen} />
25+
<MessageInput value={value} onChange={onChange} onKeyUp={onKeyUp} />
26+
<img
27+
className={`ml-4 ${value ? "cursor-pointer" : "cursor-not-allowed"}`}
28+
onClick={onSend}
29+
src={value ? "/icon/submit_action.svg" : "/icon/submit_disabled.svg"}
30+
alt="메시지 제출"
31+
width={40}
32+
height={40}
33+
/>
34+
</div>
35+
);
36+
};
37+
38+
export default ChatActionBar;

src/components/ChatMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ const ChatMessage = ({ message, myMessage }: ChatMessageProps) => {
1111
: "rounded-tl-none border border-gray-100";
1212

1313
return (
14-
<div className="flex w-full justify-center items-center mb-2">
14+
<div className="mb-[6px]">
1515
<div
16-
className={`max-w-xs px-2.5 py-3 rounded-[12px] ${borderStyle} ${backgroundColor} ${textColor}`}
16+
className={`max-w-xs rounded-[12px] px-2.5 py-3 ${borderStyle} ${backgroundColor} ${textColor}`}
1717
style={{
1818
fontWeight: 500,
1919
fontSize: "16px",

src/components/IntroGuide.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
const IntroGuide = () => {
22
return (
3-
<div className="bg-white p-5 border border-primary-light rounded-xl">
4-
<p className="font-light text-lg whitespace-pre-line">
3+
<div className="rounded-xl border border-primary-light bg-white p-5 text-black">
4+
<p className="text-md font-light whitespace-pre-line">
55
{`MBTI 대화에 참여하셨군요!
66
대화 상황에 대해 구체적으로
77
말씀해주시면,더 좋은 답변을 드릴 수 있어요 :)`}
88
</p>
9-
<strong className="block mt-2 font-medium text-lg">
9+
<p className="mt-2 block text-md font-medium">
1010
언제, 어디서, 어떤 상황인지 자유롭게 알려주세요
11-
</strong>
11+
</p>
1212
</div>
1313
);
1414
};

src/components/input/MessageInput.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { ChangeEvent } from "react";
1+
import { ChangeEvent, KeyboardEvent } from "react";
22

33
interface MessageInputProps {
44
value: string;
55
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
6+
onKeyUp: (e: KeyboardEvent<HTMLInputElement>) => void;
67
}
78

8-
const MessageInput = ({ value, onChange }: MessageInputProps) => {
9+
const MessageInput = ({ value, onChange, onKeyUp }: MessageInputProps) => {
910
return (
1011
<input
11-
className="bg-gray-50 ml-2.5 px-4 py-2.5 rounded-[40px] w-[257px] h-[44px] font-medium text-2lg text-gray-900 placeholder:text-gray-600"
12-
placeholder="메시지를 입력해주세요."
12+
type="text"
13+
className="ml-2.5 flex h-[44px] w-[242px] justify-center rounded-[40px] bg-gray-50 py-2.5 pl-4 text-lg leading-[24px] font-medium text-gray-900 placeholder:text-gray-600 md:w-[257px] lg:w-[382px]"
14+
placeholder="메시지를 입력해주세요"
1315
value={value}
1416
onChange={onChange}
17+
onKeyUp={onKeyUp}
1518
/>
1619
);
1720
};

src/pages/Chat.tsx

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,121 @@
1+
import { useEffect, useRef, useState, ChangeEvent, KeyboardEvent } from "react";
2+
import IntroGuide from "@/components/IntroGuide";
3+
import Header from "@/components/Header";
4+
import ChatMessage from "@/components/ChatMessage";
5+
import ChatActionBar from "@/components/ChatActionBar";
6+
import pickMbtiImage from "@/utils/pickMbtiImage";
7+
import instance from "@/api/axios";
8+
9+
interface Message {
10+
role: "user" | "assistant";
11+
content: string;
12+
}
13+
114
const Chat = () => {
2-
return <div>Chat 페이지!</div>;
15+
const [messages, setMessages] = useState<Message[]>([]);
16+
const [input, setInput] = useState("");
17+
const [isOpen, setIsOpen] = useState(false);
18+
const bottomRef = useRef<HTMLDivElement | null>(null);
19+
20+
useEffect(() => {
21+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
22+
}, [messages]);
23+
24+
const chatTitle = "ENFP와 대화"; //TODO: API 연동 후 수정 필요
25+
const assistantInfo = "ENFP"; //TODO: API 연동 후 수정 필요
26+
const assistantImgUrl = pickMbtiImage(assistantInfo);
27+
28+
const handleSend = async (messageToSend: string) => {
29+
if (!messageToSend.trim()) return;
30+
31+
const newMessages: Message[] = [
32+
...messages,
33+
{ role: "user", content: messageToSend }
34+
];
35+
setMessages(newMessages);
36+
setInput("");
37+
38+
try {
39+
//TODO: API 분기처리 필요
40+
const response = await instance.post(
41+
"/api/fast-friend/message",
42+
JSON.stringify({ content: messageToSend })
43+
);
44+
45+
setMessages([
46+
...newMessages,
47+
{
48+
role: "assistant",
49+
content: JSON.stringify(response.data) || "응답이 없어요"
50+
}
51+
]);
52+
} catch (e) {
53+
setMessages([
54+
...newMessages,
55+
{ role: "assistant", content: "오류가 발생했어요. 다시 시도해 주세요." }
56+
]);
57+
}
58+
};
59+
60+
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
61+
setInput(e.target.value);
62+
};
63+
64+
const handleKeyup = (e: KeyboardEvent<HTMLInputElement>) => {
65+
if (e.key === "Enter") {
66+
handleSend(e.currentTarget.value);
67+
}
68+
};
69+
70+
return (
71+
<div className="flex w-[360px] flex-col bg-white md:w-[375px] lg:w-[500px]">
72+
<Header title={chatTitle} />
73+
74+
<div className="mt-6 flex-1 space-y-4 overflow-y-auto px-[20px]">
75+
<div>
76+
<IntroGuide />
77+
</div>
78+
79+
{/* 메시지 리스트 */}
80+
{messages.map((msg, index) => (
81+
<div
82+
key={index}
83+
className={`flex ${
84+
msg.role === "user" ? "justify-end" : "justify-start"
85+
} items-start`}
86+
>
87+
{/* 캐릭터 아이콘 */}
88+
{msg.role === "assistant" && (
89+
<img
90+
src={assistantImgUrl}
91+
alt="MBTI ICON"
92+
className="mr-[9px] h-[36px] w-[36px] shrink-0 rounded-full border border-gray-200"
93+
/>
94+
)}
95+
96+
{/* 채팅 메시지 */}
97+
<div className="mt-3.5">
98+
<ChatMessage
99+
message={msg.content}
100+
myMessage={msg.role === "user"}
101+
/>
102+
</div>
103+
</div>
104+
))}
105+
106+
<div ref={bottomRef} />
107+
</div>
108+
109+
<ChatActionBar
110+
isOpen={isOpen}
111+
setIsOpen={setIsOpen}
112+
value={input}
113+
onChange={handleChange}
114+
onKeyUp={handleKeyup}
115+
onSend={() => handleSend(input)}
116+
/>
117+
</div>
118+
);
3119
};
4120

5121
export default Chat;

src/pages/SelectInfo.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { ChangeEvent, useState } from "react";
22
import { useLocation } from "react-router-dom";
33
import FormButton from "@/components/button/FormButton";
44
import Header from "@/components/Header";
@@ -85,7 +85,7 @@ const SelectInfo = () => {
8585
return interest.includes(option);
8686
};
8787

88-
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
88+
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
8989
setName(e.target.value.substring(0, 6));
9090
};
9191

0 commit comments

Comments
 (0)