Skip to content

Commit b75f117

Browse files
committed
mod: main 브랜치 병합 및 라우트 폴더 구조 통일
- origin/main 병합 (chatbot 페이지 포함) - seller-profile.tsx → seller/index.tsx 이동 - chatbot.tsx → chatbot/index.tsx 이동 - 모든 라우트 파일 폴더 구조로 통일
2 parents d54cf5f + adacff9 commit b75f117

9 files changed

Lines changed: 180 additions & 5 deletions

File tree

src/app/routes.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export default [
1818
route('mypage/settings', 'routes/(main)/mypage/settings.tsx'),
1919
route('mypage/profile', 'routes/(main)/mypage/profile.tsx'),
2020

21-
// seller-profile
22-
route('seller/:userId', 'routes/(main)/seller-profile.tsx'),
21+
// seller
22+
route('seller/:userId', 'routes/(main)/seller/index.tsx'),
23+
24+
// chatbot
25+
route('chatbot', 'routes/(main)/chatbot/index.tsx'),
2326
]),
2427
] satisfies RouteConfig;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { ChatbotPage } from '@pages/chatbot';
2+
3+
export default ChatbotPage;

src/pages/chatbot/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ChatbotPage } from './ui/ChatbotPage';

src/pages/chatbot/model/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type ChatMessageStatus = 'loading' | 'done';
2+
3+
export type ChatMessageRole = 'bot' | 'user';
4+
5+
export type ChatMessage = {
6+
id: string;
7+
role: ChatMessageRole;
8+
content: string;
9+
status?: ChatMessageStatus;
10+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ChatBubble } from '@shared/ui/ChatBubble';
2+
import type { ChatMessage } from '../model/types';
3+
4+
type ChatMessageListProps = {
5+
messages: ChatMessage[];
6+
};
7+
8+
export const ChatMessageList = ({ messages }: ChatMessageListProps) => {
9+
if (messages.length === 0) {
10+
return null;
11+
}
12+
13+
return (
14+
<div className="flex w-full flex-col items-end gap-[64px]">
15+
{messages.map((message) => {
16+
const isBot = message.role === 'bot';
17+
if (isBot) {
18+
return (
19+
<div key={message.id} className="flex w-full justify-start">
20+
{message.status === 'loading' ? (
21+
<div className="inline-flex min-h-[60px] items-center justify-start gap-[var(--spacing-xxxs)] rounded-(--radius-l) bg-gray-100 px-[31px] py-[18px]">
22+
<span className="h-[6px] w-[6px] animate-bounce rounded-full bg-gray-400 [animation-delay:-0.2s]" />
23+
<span className="h-[6px] w-[6px] animate-bounce rounded-full bg-gray-400 [animation-delay:-0.1s]" />
24+
<span className="h-[6px] w-[6px] animate-bounce rounded-full bg-gray-400" />
25+
</div>
26+
) : (
27+
<ChatBubble message={message.content} variant="chatbotNotice" />
28+
)}
29+
</div>
30+
);
31+
}
32+
33+
return (
34+
<div key={message.id} className="flex w-full items-end justify-end">
35+
<ChatBubble message={message.content} variant="sender" className="w-auto" />
36+
</div>
37+
);
38+
})}
39+
</div>
40+
);
41+
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Logo4 } from '@shared/assets/logo';
2+
import { ChatInput } from '@shared/ui/ChatInput';
3+
import { useEffect, useRef, useState } from 'react';
4+
import { ChatMessageList } from './ChatMessageList';
5+
import type { ChatMessage } from '../model/types';
6+
7+
const INITIAL_BOT_MESSAGE = `루핏이 예상 수리비를 빠르게 계산해드릴게요. 아래 3가지만 알려주세요.
8+
(견적은 추정치이며 실제 비용은 수리점/부품/상태에 따라 달라질 수 있어요.)
9+
10+
기종: 예) 아이폰 15, 갤럭시 S23
11+
12+
어디가 고장났는지: 예) 액정/배터리/카메라/후면/충전/침수
13+
14+
보험/케어 가입 여부: 예) 애플케어 O/X, 통신사 보험 O/X
15+
16+
가능하면\u00A0원하는\u00A0방향도\u00A0한\u00A0줄로\u00A0적어주세요:\u00A0“정품\u00A0우선”\u00A0/\u00A0“최대한\u00A0저렴하\u2060게”\u00A0/\u00A0“빨리”
17+
18+
예시) “아이폰 15, 액정 깨짐, 애플케어 X, 최대한 저렴하게”
19+
20+
`;
21+
22+
const ChatbotPage = () => {
23+
const [message, setMessage] = useState('');
24+
const [messages, setMessages] = useState<ChatMessage[]>(() => [
25+
{
26+
id: 'bot-initial',
27+
role: 'bot' as const,
28+
content: INITIAL_BOT_MESSAGE,
29+
status: 'done',
30+
},
31+
]);
32+
const replyTimeoutRef = useRef<number | null>(null);
33+
const endOfMessagesRef = useRef<HTMLDivElement | null>(null);
34+
35+
useEffect(() => {
36+
return () => {
37+
if (replyTimeoutRef.current) {
38+
window.clearTimeout(replyTimeoutRef.current);
39+
}
40+
};
41+
}, []);
42+
43+
useEffect(() => {
44+
endOfMessagesRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
45+
}, [messages]);
46+
47+
const handleSend = (value: string) => {
48+
const trimmed = value.trim();
49+
if (!trimmed) {
50+
return;
51+
}
52+
53+
const userId = `user-${Date.now()}`;
54+
const botId = `bot-${Date.now()}`;
55+
setMessages((prev) => [
56+
...prev,
57+
{ id: userId, role: 'user', content: trimmed, status: 'done' },
58+
{ id: botId, role: 'bot', content: '답변을 준비 중이에요...', status: 'loading' },
59+
]);
60+
setMessage('');
61+
62+
if (replyTimeoutRef.current) {
63+
window.clearTimeout(replyTimeoutRef.current);
64+
}
65+
replyTimeoutRef.current = window.setTimeout(() => {
66+
setMessages((prev) =>
67+
prev.map((item) =>
68+
item.id === botId
69+
? {
70+
...item,
71+
status: 'done',
72+
content: '입력하신 내용으로 예상 수리비를 준비할게요.\n필요하면 기종/증상 상세를 더 알려주세요!',
73+
}
74+
: item
75+
)
76+
);
77+
}, 800);
78+
};
79+
80+
return (
81+
<div className="w-full bg-white">
82+
<div className="mx-auto flex min-h-screen w-full max-w-[1440px] flex-col bg-white xl:min-h-[1024px]">
83+
<main className="flex flex-1 flex-col px-[var(--margin-l)] md:px-[var(--spacing-xxxl)] xl:px-0">
84+
<section className="mx-auto flex w-full max-w-full flex-col items-start gap-[19px] self-stretch pb-[140px] xl:max-w-[1200px]">
85+
<div className="flex items-center gap-[var(--spacing-m)] md:gap-[var(--spacing-xl)] xl:gap-[36px]">
86+
<div className="flex h-[64px] w-[64px] flex-shrink-0 flex-col items-center justify-center gap-[10px] rounded-full bg-black pt-[var(--padding-xl)] pr-[10px] pb-[var(--padding-xl)] pl-[14px] md:h-[72px] md:w-[72px] md:pt-[26px] md:pr-[11px] md:pb-[28px] md:pl-[15px] xl:h-[80px] xl:w-[80px] xl:pt-[31px] xl:pr-[13px] xl:pb-[33px] xl:pl-[17px]">
87+
<Logo4 className="h-[80px] w-[80px] flex-shrink-0" aria-hidden="true" />
88+
</div>
89+
<h1 className="typo-title-2 text-black">루핏봇</h1>
90+
</div>
91+
<ChatMessageList messages={messages} />
92+
<div ref={endOfMessagesRef} className="scroll-mb-[96px]" />
93+
</section>
94+
<div className="fixed inset-x-0 bottom-[var(--margin-s)] z-50 px-[var(--margin-l)] md:px-[var(--spacing-xxxl)] xl:px-[120px]">
95+
<div className="mx-auto w-full max-w-full xl:max-w-[1200px]">
96+
<ChatInput
97+
placeholder="무슨 견적을 원하시나요?"
98+
value={message}
99+
onChange={setMessage}
100+
onSend={handleSend}
101+
/>
102+
</div>
103+
</div>
104+
</main>
105+
</div>
106+
</div>
107+
);
108+
};
109+
110+
export default ChatbotPage;

src/shared/assets/logo/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Logo from './logo.svg?react';
22
import Logo2 from './logo2.svg?react';
33
import Logo3 from './logo3.svg?react';
4+
import Logo4 from './logo4.svg?react';
45

5-
export { Logo, Logo2, Logo3 };
6+
export { Logo, Logo2, Logo3, Logo4 };

src/shared/ui/ChatBubble/ChatBubble.variants.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ export const chatBubbleVariants = tv({
66
bubble: [
77
'inline-flex',
88
'items-center',
9-
'justify-center',
9+
'justify-start',
1010
'min-h-[60px]',
1111
'px-[31px]',
1212
'py-[18px]',
13-
'rounded-(--radius-l)',
13+
'rounded-[var(--radius-l)]',
1414
'max-w-[559px]',
1515
],
1616
text: ['typo-body-1', 'whitespace-pre-wrap', 'break-words'],
@@ -31,6 +31,12 @@ export const chatBubbleVariants = tv({
3131
text: ['text-gray-900'],
3232
meta: ['order-last'],
3333
},
34+
chatbotNotice: {
35+
root: ['justify-start'],
36+
bubble: ['bg-gray-100', 'max-w-[720px]'],
37+
text: ['text-gray-900', 'whitespace-pre-line', 'break-keep'],
38+
meta: ['order-last'],
39+
},
3440
},
3541
},
3642

0 commit comments

Comments
 (0)