Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/domains/login/components/WelcomeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ function Welcome({ userNickname, open, onClose }: Props) {
<ModalLayout
open={open}
onClose={onClose}
title={`환영합니다, ${userNickname}님!`}
description="바텐더 쑤리가 안내해드릴게요"
title={`환영합니다! `}
description={
<span className="block text-center">
{userNickname}님 <br /> 바텐더 쑤리가 안내해드릴게요
</span>
}
buttons={
<>
<Button
Expand All @@ -48,7 +52,7 @@ function Welcome({ userNickname, open, onClose }: Props) {
>
<div className="flex-center">
<div className="relative w-32 h-32" aria-hidden>
<Image src={Ssury} alt="" fill sizes="128px" className="object-contain" />
<Image src={Ssury} alt="" fill className="object-contain" sizes="8rem" priority />
</div>
</div>
</ModalLayout>
Expand Down
85 changes: 73 additions & 12 deletions src/domains/recommend/components/ChatSection.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,87 @@
'use client';

import { useState } from 'react';
import BotMessage from './BotMessage';
import UserMessage from './UserMessage';
import MessageInput from './MessageInput';
import { useEffect, useRef, useState } from 'react';
import BotMessage from './bot/BotMessage';
import UserMessage from './user/UserMessage';
import NewMessageAlert from './bot/NewMessageAlert';
import MessageInput from './user/MessageInput';

// TODOS : 아직 api 몰라서 임시 type
interface ChatMessage {
id: number;
message: string;
sender: 'user' | 'bot';
}

function ChatSection() {
const [messages, setMessages] = useState<string[]>([]);
const [messages, setMessages] = useState<ChatMessage[]>([]);
const chatEndRef = useRef<HTMLDivElement>(null);
const chatListRef = useRef<HTMLDivElement>(null);
const isScrollBottom = useRef(true);
const [showNewMessageAlert, setShowNewMessageAlert] = useState(false);

const handleSubmit = (message: string) => {
setMessages((prev) => [...prev, message]);
// 사용자 메시지
setMessages((prev) => [...prev, { id: prev.length + 1, message, sender: 'user' }]);
};

// 쑤리 임시 메시지
// useEffect(() => {
// const interval = setInterval(() => {
// setMessages((prev) => [
// ...prev,
// { id: prev.length + 1, message: `새 메시지 ${prev.length + 1}`, sender: 'bot' },
// ]);
// }, 3000);

// return () => clearInterval(interval);
// }, []);

// 스크롤 제일 아래인지 체크
const handleCheckBottom = (e: React.UIEvent<HTMLDivElement>) => {
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;

isScrollBottom.current = scrollTop + clientHeight >= scrollHeight - 10;

if (isScrollBottom.current) setShowNewMessageAlert(false);
};

// 새 메시지가 들어오면 자동 스크롤
useEffect(() => {
if (isScrollBottom.current) {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
setShowNewMessageAlert(false); // 새메세지 숨김
} else {
setShowNewMessageAlert(true); // 새메세지 보여줌
}
}, [messages]);

// 스크롤 제일 아래로
const handleScrollToBottom = () => {
if (chatListRef.current) {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
isScrollBottom.current = true;
}
};

return (
<section className="page-layout max-w-1024 py-12 ">
<section className="mx-auto w-full flex-1">
<h2 className="sr-only">대화 목록 및 입력 창</h2>
<div className="flex flex-col gap-10 pb-20">
<BotMessage />
<div
ref={chatListRef}
onScroll={handleCheckBottom}
className="flex flex-col gap-10 pt-12 px-3 overflow-y-auto max-h-[calc(100vh-116px)] md:max-h-[calc(100vh-144px)]"
>
{messages.map(({ id, message, sender }) =>
sender === 'user' ? (
<UserMessage key={id} message={message} />
) : (
<BotMessage key={id} messages={[{ id, type: 'text', message }]} />
)
)}

{messages.map((msg, i) => (
<UserMessage key={i} message={msg} />
))}
<div ref={chatEndRef}></div>
{showNewMessageAlert && <NewMessageAlert onClick={handleScrollToBottom} />}
</div>
<MessageInput onSubmit={handleSubmit} />
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,26 @@ import BotOptions from './BotOptions';
import TypingIndicator from './TypingIndicator';

interface Message {
id: string;
text?: string;
id: number;
message?: string;
type?: 'radio' | 'text' | 'recommend';
}

function BotMessage() {
interface BotMessages {
messages: Message[];
isTyping?: boolean;
}

function BotMessage({ messages, isTyping = false }: BotMessages) {
const [selected, setSelected] = useState('option1');

// radio 옵션
// 임시 radio 옵션
const options = [
{ label: '옵션 1', value: 'option1' },
{ label: '옵션 2', value: 'option2' },
{ label: '옵션 3', value: 'option3' },
];

// 메시지 (연속 메시지)
const messages: Message[] = [
{
id: '1',
text: '안녕하세요, 바텐더 쑤리에요. \n 취향에 맞는 칵테일을 추천해드릴게요!',
},
{
id: '2',
text: '어떤 유형으로 찾아드릴까요?',
type: 'radio',
},
{
id: '3',
type: 'recommend',
},
];

return (
<article aria-label="취향추천 챗봇 메시지" className="">
<header className="flex items-end">
Expand Down Expand Up @@ -73,7 +61,7 @@ function BotMessage() {
</ul>
) : (
<div className="flex flex-col w-fit max-w-[80%] min-w-[120px] p-3 rounded-2xl rounded-tl-none bg-white text-black">
{msg.text && <p className="whitespace-pre-line">{msg.text}</p>}
{msg.message && <p className="whitespace-pre-line">{msg.message}</p>}

{/* radio */}
{msg.type === 'radio' && (
Expand All @@ -83,7 +71,7 @@ function BotMessage() {
)}
</div>
))}
<TypingIndicator />
{isTyping && <TypingIndicator />}
</div>
</article>
);
Expand Down
23 changes: 23 additions & 0 deletions src/domains/recommend/components/bot/NewMessageAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';
import Down from '@/shared/assets/icons/selectDown_24.svg';

interface Props {
onClick: () => void;
}

function NewMessageAlert({ onClick }: Props) {
return (
<button
className="absolute left-1/2 bottom-[90px] -translate-x-1/2 flex-center gap-1
bg-white text-primary rounded-full px-2 py-1 shadow-[0_2px_4px_rgba(255,255,255,0.3)]
hover:bg-tertiary hover:text-white active:bg-tertiary active:text-white
transition-colors duration-200 ease-in-out"
onClick={onClick}
aria-label="새 메시지로 이동"
>
<span className="text-sm">새 메시지</span>
<Down className="w-6 h-6 text-inherit" />
</button>
);
}
export default NewMessageAlert;