Skip to content

Commit

Permalink
feat: 메세지 입력 컴포넌트에서 전송 기능 삭제 & 메인페이지에서 처리
Browse files Browse the repository at this point in the history
  • Loading branch information
gogumalatte committed Nov 9, 2024
1 parent 608de47 commit 8e9d0cd
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 184 deletions.
324 changes: 177 additions & 147 deletions src/pages/main/index.tsx
Original file line number Diff line number Diff line change
@@ -1,147 +1,177 @@
import { useState, useEffect, useRef } from "react";
import { MessageInput } from "./message-input";
import { MessageList } from "./message-list";
import { Sidebar } from "./sidebar";
import styled from "@emotion/styled";
import tutorial from "@/assets/tutorial.svg";

const MainPage = () => {
const [messages, setMessages] = useState<string[]>([]);
const [inputMessage, setInputMessage] = useState("");
const [isSidebarOpen, setSidebarOpen] = useState(false);
const [inputHeight, setInputHeight] = useState(36); // 메시지 입력창 높이 상태 추가
const pageRef = useRef<HTMLDivElement>(null);

const handleSendMessage = () => {
if (inputMessage.trim()) {
setMessages((prev) => [inputMessage, ...prev]);
setInputMessage(""); // 상태가 업데이트된 후에 입력창 초기화
}
};

// 메시지가 추가될 때 페이지를 가장 아래로 스크롤
useEffect(() => {
if (pageRef.current) {
pageRef.current.scrollTo({
top: pageRef.current.scrollHeight,
behavior: "smooth",
});
}
}, [messages, inputHeight]); // inputHeight 추가하여 메시지 입력창 높이 변경에 따른 스크롤 업데이트

const toggleSidebar = () => {
setSidebarOpen((prev) => !prev);
};

return (
<PageWrapper ref={pageRef}>
<SidebarWrapper isOpen={isSidebarOpen}>
<Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
</SidebarWrapper>

<ContentWrapper inputHeight={inputHeight}>
{messages.length === 0 ? (
<TutorialImageWrapper>
<img src={tutorial} alt="튜토리얼 이미지" />
</TutorialImageWrapper>
) : (
<MessageListWrapper>
<MessageList messages={messages} />
</MessageListWrapper>
)}
<MessageInputWrapper>
<MessageInput
inputMessage={inputMessage}
setInputMessage={setInputMessage}
onSendMessage={handleSendMessage}
setInputHeight={setInputHeight} // 입력창 높이 업데이트 함수 전달
/>
</MessageInputWrapper>
</ContentWrapper>
</PageWrapper>
);
};

export default MainPage;

// 전체 페이지 래퍼 (스크롤 가능한 요소)
const PageWrapper = styled.div`
position: relative;
height: 100vh;
overflow-y: auto; // 전체 페이지에 스크롤이 생긴도록 설정
display: flex;
flex-direction: column;
`;

// SidebarWrapper 스타일 컨텐츠
const SidebarWrapper = styled.div<{ isOpen: boolean }>`
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: ${({ isOpen }) => (isOpen ? "250px" : "0")};
overflow: hidden;
transition: width 0.3s ease-in-out;
background-color: #f0f0f0;
z-index: 1000;
`;

// ContentWrapper 스타일 컨텐츠
const ContentWrapper = styled.div<{ inputHeight: number }>`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
margin-bottom: ${({ inputHeight }) =>
Math.min(
inputHeight - 40,
120
)}px; // 메시지 입력창 높이에 따른 여백 동적 조정
transition: margin-left 0.3s ease-in-out;
`;

// MessageListWrapper 스타일 컨텐츠 (메시지 리스트를 포함)
const MessageListWrapper = styled.div`
width: 100%;
flex-grow: 1;
overflow-y: auto;
margin-bottom: 20px; // 메시지 입력창과 메시지 리스트 간 여백 추가
`;

// TutorialImageWrapper 스타일 컨텐츠
const TutorialImageWrapper = styled.div`
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
width: 100%;
img {
max-width: 100%;
max-height: 400px;
object-fit: contain;
@media (max-width: 768px) {
max-width: 90vw;
object-fit: contain;
}
@media (max-width: 480px) {
max-width: 80vw;
object-fit: contain;
}
}
`;

// MessageInputWrapper 스타일 컨텐츠
const MessageInputWrapper = styled.div`
width: 90%;
max-width: 800px;
margin-top: 20px;
position: relative;
margin-bottom: 20px; // 메시지 입력창 아래 여백 추가
`;
import { useState, useEffect, useRef } from "react";
import { MessageInput } from "./message-input";
import { MessageList } from "./message-list";
import { Sidebar } from "./sidebar";
import styled from "@emotion/styled";
import tutorial from "@/assets/tutorial.svg";

const MainPage = () => {
const [messages, setMessages] = useState<string[]>([]);
const [inputMessage, setInputMessage] = useState("");
const [isSidebarOpen, setSidebarOpen] = useState(false);
const [inputHeight, setInputHeight] = useState(36);
const [loading, setLoading] = useState(false); // 로딩 상태 추가
const pageRef = useRef<HTMLDivElement>(null);
const baseURL = import.meta.env.VITE_BASE_URL;

const handleSendMessage = async () => {
if (inputMessage.trim() && !loading) {
// 로딩 중일 때는 전송을 방지
const userMessage = inputMessage;
setMessages((prev) => [userMessage, ...prev]);
setInputMessage("");

setLoading(true); // 로딩 상태 활성화

try {
const response = await fetch(`${baseURL}/api/front-ai-response`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ question: userMessage }),
});

if (!response.ok) {
throw new Error("Failed to fetch AI response");
}

const data = await response.json();
const aiResponse = data.response;
setMessages((prev) => [aiResponse, ...prev]);
} catch (error) {
console.error("Error while sending message:", error);
} finally {
setLoading(false); // 로딩 상태 비활성화
}
}
};

useEffect(() => {
if (pageRef.current) {
pageRef.current.scrollTo({
top: pageRef.current.scrollHeight,
behavior: "smooth",
});
}
}, [messages, inputHeight]);

const toggleSidebar = () => {
setSidebarOpen((prev) => !prev);
};

return (
<PageWrapper ref={pageRef}>
<SidebarWrapper isOpen={isSidebarOpen}>
<Sidebar isOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />
</SidebarWrapper>

<ContentWrapper inputHeight={inputHeight}>
{messages.length === 0 ? (
<TutorialImageWrapper>
<img src={tutorial} alt="튜토리얼 이미지" />
</TutorialImageWrapper>
) : (
<MessageListWrapper>
<MessageList messages={messages} />
{loading && (
<LoadingMessage>응답을 기다리는 중...</LoadingMessage>
)}{" "}
{/* 로딩 메시지 표시 */}
</MessageListWrapper>
)}
<MessageInputWrapper>
<MessageInput
inputMessage={inputMessage}
setInputMessage={setInputMessage}
onSendMessage={handleSendMessage}
setInputHeight={setInputHeight}
isLoading={loading} // 로딩 상태 전달
/>
</MessageInputWrapper>
</ContentWrapper>
</PageWrapper>
);
};

export default MainPage;

// 스타일은 그대로 유지
const PageWrapper = styled.div`
position: relative;
height: 100vh;
overflow-y: auto;
display: flex;
flex-direction: column;
`;

const SidebarWrapper = styled.div<{ isOpen: boolean }>`
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: ${({ isOpen }) => (isOpen ? "250px" : "0")};
overflow: hidden;
transition: width 0.3s ease-in-out;
background-color: #f0f0f0;
z-index: 1000;
`;

const ContentWrapper = styled.div<{ inputHeight: number }>`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
margin-bottom: ${({ inputHeight }) => Math.min(inputHeight - 40, 120)}px;
transition: margin-left 0.3s ease-in-out;
`;

const MessageListWrapper = styled.div`
width: 100%;
flex-grow: 1;
overflow-y: auto;
margin-bottom: 20px;
`;

const TutorialImageWrapper = styled.div`
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
justify-content: center;
align-items: center;
width: 100%;
img {
max-width: 100%;
max-height: 400px;
object-fit: contain;
@media (max-width: 768px) {
max-width: 90vw;
object-fit: contain;
}
@media (max-width: 480px) {
max-width: 80vw;
object-fit: contain;
}
}
`;

const MessageInputWrapper = styled.div`
width: 90%;
max-width: 800px;
margin-top: 20px;
position: relative;
margin-bottom: 20px;
`;

const LoadingMessage = styled.div`
font-size: 14px;
color: #666;
margin-top: 10px;
text-align: center;
`;
Loading

0 comments on commit 8e9d0cd

Please sign in to comment.