Skip to content
Merged
36 changes: 1 addition & 35 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type FormEvent, useState } from "react";
import { Route, Routes } from "react-router-dom";
import styles from "./App.module.css";
import { ChatWidget } from "./components/organisms";
Expand All @@ -22,38 +21,12 @@ import {
SignUp,
} from "./pages";

import type { Message } from "./types/types";

function GlobalAppSetup() {
useTokenExpirationCheck();
return null;
}

export default function App() {
const [isChatOpen, setIsChatOpen] = useState(false);
const [messages, setMessages] = useState<Message[]>([
{ id: 1, text: "안녕하세요! 무엇을 도와드릴까요?", isUser: false },
]);
const [newMessage, setNewMessage] = useState("");

const handleSendMessage = (e: FormEvent) => {
e.preventDefault();
if (!newMessage.trim()) return;
const nextId = messages.length
? Math.max(...messages.map((m) => m.id)) + 1
: 1;
setMessages([
...messages,
{ id: nextId, text: newMessage, isUser: true },
{
id: nextId + 1,
text: "죄송합니다. 지금은 상담이 불가능합니다. 상담원 연결은 평일 09:00~18:00에 가능합니다.",
isUser: false,
},
]);
setNewMessage("");
};

return (
<div className={styles.app}>
<ToastProvider>
Expand All @@ -79,14 +52,7 @@ export default function App() {
<Route path="/mypage/:tab?" element={<Mypage />} />
</Routes>

<ChatWidget
isOpen={isChatOpen}
messages={messages}
newMessage={newMessage}
onToggle={() => setIsChatOpen(!isChatOpen)}
onChange={setNewMessage}
onSend={handleSendMessage}
/>
<ChatWidget />
</MainTemplate>
</ModalProvider>
</CategoryProvider>
Expand Down
24 changes: 24 additions & 0 deletions src/api/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { api } from "../lib/axios";
import type { Product } from "../types/chat";

type ChatResponseData = {
chatbotMessage: string;
recommendedProducts: Product[];
};

/**
* 챗봇 메시지를 백엔드로 전송하고 AI의 응답을 받아오는 함수
* @param query 사용자가 입력한 메시지
* @returns AI가 생성한 답변 데이터
*/
export async function postChatMessage(
query: string,
): Promise<ChatResponseData> {
const res = await api.post(
"/chat", // 👈 baseURL이 빠진 상대 경로만 사용
{ query }, // Request Body
);

// 👈 mainProducts.ts와 동일한 데이터 반환 구조
return res.data; // as ChatResponseData;
}
113 changes: 98 additions & 15 deletions src/components/organisms/ChatWidget/ChatWidget.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,6 @@
flex-direction: column;
gap: 1rem;
}
.msg {
padding: 0.75rem;
}
.user {
align-self: flex-end;
color: var(--color-white);
border-radius: 10px 10px 0 10px;
background-color: var(--color-black);
}
.bot {
align-self: flex-start;
border-radius: 10px 10px 10px 0;
color: var(--color-black);
background-color: var(--color-gray-200);
}
.form {
display: flex;
gap: 0.5rem;
Expand Down Expand Up @@ -89,3 +74,101 @@
.send:hover {
background-color: var(--color-navy);
}
.messageRow {
display: flex;
align-items: flex-end; /* 아바타와 말풍선 하단을 정렬 */
margin-bottom: 12px;
gap: 8px; /* 아바타와 말풍선 사이 간격 */
}

/* 봇 메시지 행 스타일 */
.botRow {
justify-content: flex-start; /* 왼쪽 정렬 */
}

/* 사용자 메시지 행 스타일 */
.userRow {
justify-content: flex-end; /* 오른쪽 정렬 */
}

/* 봇 로고(아바타) 이미지 스타일 */
.avatar {
width: 32px;
height: 32px;
border-radius: 50%; /* 원형으로 만들기 */
object-fit: cover;
background-color: var(--color-white); /* 이미지가 없을 경우를 대비한 배경색 */
}

/* 말풍선 기본 스타일 */
.bubble {
max-width: 75%;
padding: 10px 14px;
border-radius: 18px;
word-break: break-word;
font-size: 0.9rem;
line-height: 1.5;
}

/* 봇 말풍선 스타일 */
.botBubble {
background-color: var(--color-gray-200); /* 옅은 회색 배경 */
color: var(--color-text);
border-bottom-left-radius: 4px;
}

/* 사용자 말풍선 스타일 */
.userBubble {
background-color: var(--color-black);
color: var(--color-white);
border-bottom-right-radius: 4px;
}
.productContainer {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}

.productItem {
display: flex;
align-items: center;
gap: 12px;
padding: 8px;
border-radius: 8px;
background-color: var(--color-gray-100);
border: 1px solid var(--color-gray-300);
text-decoration: none;
color: inherit;
transition: background-color 0.2s ease;
}

.productItem:hover {
background-color: var(--color-gray-300);
}

.productImage {
width: 50px;
height: 50px;
border-radius: 4px;
object-fit: cover;
}

.productInfo {
display: flex;
flex-direction: column;
}

.productBrand {
font-size: 0.8rem;
color: var(--color-gray-800);
}

.productName {
font-size: 0.9rem;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
Loading