o2o-negosium-original/frontend/src/features/chat/components/UserButton.tsx

119 lines
5.0 KiB
TypeScript

import { useEffect } from 'react'
import { useNavigate } from 'react-router'
import { cn } from '@/lib'
import { useChatStore } from '@/features/chat/stores/useChatStore'
import { Percent, Price } from '@/features/chat/components/userInputs'
import { ExtraInfoBar } from '@/features/chat/components/ExtraInfoBar'
import { GO_TO_LIST_TEXT } from '@/features/chat/lib/userButtonConfig'
import type { UserButtonConfig } from '@/features/chat/types'
// max-[1180px]: 채팅 폭이 좁아지면 버튼 패딩·최소폭 축소, 상품목록은 아이콘만 남긴다
const style = {
black:
'flex min-w-[120px] px-[28px] max-[1180px]:min-w-[100px] max-[1180px]:px-[20px] h-[46px] bg-brand-600 hover:bg-brand-700 rounded-xl items-center justify-center text-sm font-bold text-white shadow-sm cursor-pointer whitespace-nowrap transition-all duration-200 ease-out active:scale-[0.98]',
gray: 'flex min-w-[120px] px-[28px] max-[1180px]:min-w-[100px] max-[1180px]:px-[20px] h-[46px] bg-neutral-60 hover:brightness-[0.97] rounded-xl items-center justify-center text-sm font-bold text-white cursor-pointer whitespace-nowrap transition-all ease-out active:scale-[0.98]',
white:
'flex min-w-[120px] px-[28px] max-[1180px]:min-w-[100px] max-[1180px]:px-[20px] h-[46px] bg-white hover:bg-neutral-10 rounded-xl items-center justify-center text-sm font-bold text-neutral-80 border border-border cursor-pointer whitespace-nowrap transition-all ease-out active:scale-[0.98]',
}
export function UserButton({ type, text, textList, priceErrorMessage }: UserButtonConfig) {
if (type === '') return null
// 부가정보 입력은 폼이라 가운데정렬 덱이 아니라 전체폭으로 편다. text = 저장 후 보낼 동의 문구.
if (type === 'extra-info') {
return (
<div className="w-full px-6 py-4 max-[1180px]:px-4">
<ExtraInfoBar proceedText={text || ''} />
</div>
)
}
return (
<div className="flex w-full justify-center overflow-x-auto px-6 py-4 max-[1180px]:px-4">
<div className="flex justify-center">
{type === 'one-black' && <OneBlack text={text || '확인'} />}
{type === 'one-gray' && <OneGray text={text || '확인'} />}
{type === 'black-white' && <BlackWhite textList={[textList?.[0] || '예', textList?.[1] || '아니오']} />}
{type === 'percent' && <Percent />}
{type === 'three-black' && (
<ThreeBlack textList={[textList?.[0] || '협력사배송', textList?.[1] || '지정택배배송', textList?.[2] || '픽업배송']} />
)}
{type === 'price' && <Price priceErrorMessage={priceErrorMessage} />}
{type === 'loading' && <LoadingDots />}
</div>
</div>
)
}
// agent 응답 대기 중(협상 중) 버튼 자리에 표시하는 "..." 점 애니메이션. 비활성(클릭 불가).
function LoadingDots() {
return (
<div className={cn(style.black, 'cursor-default pointer-events-none')} aria-label="협상 중" role="status">
<span className="flex gap-[6px]">
<span className="size-[8px] rounded-full bg-primary-foreground animate-bounce [animation-delay:-0.3s]" />
<span className="size-[8px] rounded-full bg-primary-foreground animate-bounce [animation-delay:-0.15s]" />
<span className="size-[8px] rounded-full bg-primary-foreground animate-bounce" />
</span>
</div>
)
}
function OneBlack({ text }: { text: string }) {
const navigate = useNavigate()
const sendMessage = useChatStore((s) => s.sendMessage)
const onClick = () => (text === GO_TO_LIST_TEXT ? navigate('/list') : sendMessage(text))
return (
<button className={style.black} onClick={onClick}>
{text}
</button>
)
}
function OneGray({ text }: { text: string }) {
const sendMessage = useChatStore((s) => s.sendMessage)
return (
<button className={style.gray} onClick={() => sendMessage(text)}>
{text}
</button>
)
}
function ThreeBlack({ textList }: { textList: [string, string, string] }) {
const sendMessage = useChatStore((s) => s.sendMessage)
return (
<div className="flex gap-3">
{textList.map((t) => (
<button key={t} className={style.black} onClick={() => sendMessage(t)}>
{t}
</button>
))}
</div>
)
}
function BlackWhite({ textList }: { textList: [string, string] }) {
const sendMessage = useChatStore((s) => s.sendMessage)
// Enter = 첫 버튼(예). 입력창이 없는 선택 단계라 전역 Enter 를 잡아도 안전하다.
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Enter' && !e.isComposing) {
e.preventDefault()
sendMessage(textList[0])
}
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [sendMessage, textList])
return (
<div className="flex gap-3">
<button className={style.black} onClick={() => sendMessage(textList[0])}>
{textList[0]}
</button>
<button className={style.white} onClick={() => sendMessage(textList[1])}>
{textList[1]}
</button>
</div>
)
}