Skip to content

Commit f699df5

Browse files
committed
[fix] 말풍선안에서 로딩 -> 원래 data 변화되도록 수정
1 parent e4402b0 commit f699df5

File tree

6 files changed

+62
-36
lines changed

6 files changed

+62
-36
lines changed

src/domains/recommend/components/ChatList.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import { useChatScroll } from '../hook/useChatScroll';
22
import { ChatListProps } from '../types/recommend';
33
import BotMessage from './bot/BotMessage';
44
import NewMessageAlert from './bot/NewMessageAlert';
5-
import TypingIndicator from './bot/TypingIndicator';
65
import UserMessage from './user/UserMessage';
76

87
function ChatList({
98
messages,
109
userCurrentStep,
1110
onSelectedOption,
1211
getRecommendations,
13-
isBotTyping,
1412
}: ChatListProps) {
1513
const { chatListRef, chatEndRef, showNewMessageAlert, handleCheckBottom, handleScrollToBottom } =
1614
useChatScroll(messages[messages.length - 1]?.id);
@@ -33,7 +31,7 @@ function ChatList({
3331
messages={[
3432
{
3533
id: msg.id,
36-
message: msg.message,
34+
message: msg.type === 'TYPING' ? '' : msg.message,
3735
type: msg.type ?? 'TEXT',
3836
options: msg.type === 'RADIO_OPTIONS' ? (msg.stepData?.options ?? []) : [],
3937
recommendations: getRecommendations(msg.type, msg.stepData),
@@ -46,8 +44,6 @@ function ChatList({
4644
);
4745
})}
4846

49-
{isBotTyping && <TypingIndicator />}
50-
5147
<div ref={chatEndRef}></div>
5248
{showNewMessageAlert && <NewMessageAlert onClick={handleScrollToBottom} />}
5349
</div>

src/domains/recommend/components/ChatSection.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,31 @@ function ChatSection() {
3030
}>({});
3131

3232
const handleSendMessage = async (payload: stepPayload | { message: string; userId: string }) => {
33-
const typingTimer = setTimeout(() => setIsBotTyping(true), 300);
33+
// Typing 임시메시지
34+
setMessages((prev) => [
35+
...prev,
36+
{
37+
id: 'typing',
38+
sender: 'CHATBOT',
39+
type: 'TYPING',
40+
message: '',
41+
createdAt: new Date().toISOString(),
42+
},
43+
]);
3444

3545
try {
36-
if (!('currentStep' in payload)) {
37-
const botMessage = await fetchSendTextMessage(payload);
38-
clearTimeout(typingTimer);
39-
setIsBotTyping(false);
40-
41-
if (!botMessage) return;
42-
setTimeout(() => setMessages((prev) => [...prev, botMessage]), 500);
43-
return;
44-
}
45-
46-
const botMessage = await fetchSendStepMessage(payload);
47-
clearTimeout(typingTimer);
48-
setIsBotTyping(false);
46+
const botMessage =
47+
'currentStep' in payload
48+
? await fetchSendStepMessage(payload)
49+
: await fetchSendTextMessage(payload);
4950

5051
if (!botMessage) return;
51-
setTimeout(() => setMessages((prev) => [...prev, botMessage]), 500);
52+
53+
// Typing 임시메시지 제거 후 실제 메시지 추가
54+
setMessages((prev) => [...prev.filter((m) => m.type !== 'TYPING'), botMessage]);
5255
} catch (err) {
53-
clearTimeout(typingTimer);
54-
setIsBotTyping(false);
5556
console.error(err);
57+
setMessages((prev) => prev.filter((m) => m.type !== 'TYPING'));
5658
}
5759
};
5860

@@ -70,7 +72,16 @@ function ChatSection() {
7072
{ id: tempId, userId, message, sender: 'USER', type: 'text', createdAt: tempCreatedAt },
7173
]);
7274

73-
await handleSendMessage({ message, userId });
75+
const nextStep = userCurrentStep === 3 ? userCurrentStep + 1 : userCurrentStep;
76+
77+
const payload: stepPayload = {
78+
currentStep: nextStep,
79+
message,
80+
userId,
81+
...selectedOptions.current,
82+
};
83+
84+
await handleSendMessage(payload);
7485
};
7586

7687
// 옵션 클릭 시

src/domains/recommend/components/bot/BotMessage.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useState } from 'react';
66
import BotCocktailCard from './BotCocktailCard';
77
import BotOptions from './BotOptions';
88
import { StepOption, StepRecommendation, RecommendationItem } from '../../types/recommend';
9+
import TypingIndicator from './TypingIndicator';
910

1011
interface BotMessage {
1112
id: string;
@@ -19,10 +20,11 @@ interface BotMessages {
1920
messages: BotMessage[];
2021
stepData?: StepRecommendation | null;
2122
currentStep?: number;
23+
children?: React.ReactNode;
2224
onSelectedOption?: (value: string) => void;
2325
}
2426

25-
function BotMessage({ messages, stepData, currentStep, onSelectedOption }: BotMessages) {
27+
function BotMessage({ messages, stepData, currentStep, onSelectedOption, children }: BotMessages) {
2628
const [selected, setSelected] = useState('');
2729

2830
return (
@@ -59,8 +61,13 @@ function BotMessage({ messages, stepData, currentStep, onSelectedOption }: BotMe
5961
))}
6062
</ul>
6163
) : (
62-
<div className="flex flex-col w-fit max-w-[80%] min-w-[120px] p-3 rounded-2xl rounded-tl-none bg-white text-black">
63-
{msg.message && <p className="whitespace-pre-line">{msg.message}</p>}
64+
<div className="flex flex-col w-fit max-w-[80%] min-w-[120px] p-3 rounded-2xl rounded-tl-none bg-white text-black opacity-0 animate-fadeIn">
65+
{msg.type === 'TYPING' ? (
66+
<TypingIndicator />
67+
) : (
68+
// 실제 메시지 내용
69+
<p className="whitespace-pre-line">{msg.message}</p>
70+
)}
6471

6572
{/* radio */}
6673
{msg.type === 'RADIO_OPTIONS' && msg.options?.length && (
@@ -75,6 +82,7 @@ function BotMessage({ messages, stepData, currentStep, onSelectedOption }: BotMe
7582
}}
7683
/>
7784
)}
85+
{/* {children} */}
7886
</div>
7987
)}
8088
</div>

src/domains/recommend/components/bot/TypingIndicator.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import shaker from '@/shared/assets/images/shaker.png';
33

44
function TypingIndicator() {
55
return (
6-
<div className="relative flex items-center w-fit ml-3 p-3 rounded-2xl rounded-tl-none bg-white text-black overflow-hidden">
6+
<div className="relative flex items-center w-fittext-black">
77
<p className="inline-block animate-fade-in">준비 중…</p>
88
<div className="relative w-10 h-10 animate-shake">
99
<Image

src/domains/recommend/hook/useChatScroll.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useRef, useState } from 'react';
22

3-
export const useChatScroll = (lastMessageId: string, isBotTyping: boolean) => {
3+
export const useChatScroll = (lastMessageId: string) => {
44
const chatEndRef = useRef<HTMLDivElement>(null);
55
const chatListRef = useRef<HTMLDivElement>(null);
66
const isScrollBottom = useRef(true);
@@ -9,24 +9,21 @@ export const useChatScroll = (lastMessageId: string, isBotTyping: boolean) => {
99
// 스크롤 제일 아래인지 체크
1010
const handleCheckBottom = (e: React.UIEvent<HTMLDivElement>) => {
1111
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
12-
1312
isScrollBottom.current = scrollTop + clientHeight >= scrollHeight - 10;
1413

1514
if (isScrollBottom.current) setShowNewMessageAlert(false);
1615
};
1716

18-
// 새 메시지가 들어오면 자동 스크롤
17+
// 새 메시지또는 로딩중 변화 시 자동 스크롤
1918
useEffect(() => {
2019
const frameId = requestAnimationFrame(() => {
21-
setTimeout(() => {
22-
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
23-
isScrollBottom.current = true;
24-
setShowNewMessageAlert(false);
25-
}, 50); // 50ms 정도 살짝 기다림
20+
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
21+
isScrollBottom.current = true;
22+
setShowNewMessageAlert(false);
2623
});
2724

2825
return () => cancelAnimationFrame(frameId);
29-
}, [lastMessageId, isBotTyping]);
26+
}, [lastMessageId]);
3027

3128
// 스크롤 제일 아래로
3229
const handleScrollToBottom = () => {

src/shared/styles/_utilities.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,18 @@
9595
.animate-shake {
9696
animation: shake 3s ease-in-out infinite;
9797
}
98+
99+
@keyframes fadeIn {
100+
from {
101+
opacity: 0;
102+
transform: translateY(5px);
103+
}
104+
to {
105+
opacity: 1;
106+
transform: translateY(0);
107+
}
108+
}
109+
.animate-fadeIn {
110+
animation: fadeIn 0.3s ease-out forwards;
111+
}
98112
}

0 commit comments

Comments
 (0)