VyaparFlow / frontend /src /components /left /ChatPanel.tsx
Dipan04's picture
Add application file
32d42b3
import { useEffect, useRef, useState } from 'react'
import { Send, Smartphone, MessageCircle } from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { deriveChatMessages } from '../../lib/simulationStore'
import { useSimulationStore } from '../../lib/simulationStore'
function cn(...inputs: (string | undefined | null | false)[]) {
return twMerge(clsx(inputs))
}
export function ChatPanel() {
const [input, setInput] = useState('')
const { source, sendChatMessage, isBotTyping, state, systemState } = useSimulationStore()
const messagesEndRef = useRef<HTMLDivElement>(null)
// Strictly backend-driven messages
const chatMessages = deriveChatMessages(systemState.events)
const isWhatsApp = source === 'whatsapp'
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [chatMessages, isBotTyping])
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || state === 'simulating') return
void sendChatMessage(input)
setInput('')
}
return (
<div className="flex h-full flex-col rounded-md border border-divider/40 bg-[#0a0a0c] relative font-sans shadow-inner overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between border-b border-divider/40 bg-[#121218] px-4 py-2.5 shrink-0">
<h3 className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-[0.2em] text-text/80">
{isWhatsApp ? (
<span className="flex items-center gap-2 text-[#00a884]">
<MessageCircle size={12} strokeWidth={3} /> WhatsApp operational
</span>
) : (
<span className="flex items-center gap-2 text-text-muted">
<Smartphone size={12} strokeWidth={3} /> Input Stream
</span>
)}
</h3>
{state === 'simulating' && (
<div className="flex items-center gap-1.5 px-2 py-0.5 rounded-sm border border-blue-500/30 bg-blue-500/10">
<span className="w-1 h-1 rounded-full bg-blue-400 animate-pulse"></span>
<span className="text-[8px] font-bold uppercase tracking-widest text-blue-400">Processing</span>
</div>
)}
</div>
{/* Messages */}
<div className="custom-scrollbar flex-1 space-y-4 overflow-y-auto p-4 font-sans text-sm pb-8">
<AnimatePresence initial={false}>
{chatMessages.length === 0 && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="flex h-full flex-col items-center justify-center space-y-2 opacity-30">
<span className="text-[10px] font-mono uppercase tracking-[0.3em]">[Awaiting Input]</span>
</motion.div>
)}
{chatMessages.map((msg) => (
<motion.div
key={msg.id}
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
className={cn(
"flex max-w-[85%] flex-col gap-1",
msg.sender === 'user' ? "self-end items-end" : "self-start items-start"
)}
>
<div className={cn(
'rounded-md px-3 py-2 text-[13px] leading-relaxed shadow-sm border border-white/5',
msg.sender === 'user'
? 'bg-blue-600/20 text-blue-100 border-blue-500/20'
: isWhatsApp ? 'bg-[#202c33] text-gray-100' : 'bg-surface text-text'
)}>
{msg.text}
</div>
<span className="text-[9px] text-text-muted/40 font-mono px-1">
{msg.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
</motion.div>
))}
{isBotTyping && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="self-start flex gap-1 items-center px-4 py-2"
>
<div className="flex gap-1">
<span className="w-1 h-1 rounded-full bg-text-muted/40 animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="w-1 h-1 rounded-full bg-text-muted/40 animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="w-1 h-1 rounded-full bg-text-muted/40 animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
</motion.div>
)}
</AnimatePresence>
<div ref={messagesEndRef} />
</div>
{/* Input Area */}
<div className="border-t border-divider/40 bg-[#121218] p-3">
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={state === 'simulating'}
placeholder={isWhatsApp ? "Send business command..." : "Type a message..."}
className="flex-1 rounded-sm border border-divider/40 bg-black/40 px-3 py-2 text-xs font-medium text-text transition-all placeholder:text-text-muted/40 focus:border-white/20 focus:outline-none disabled:opacity-50"
/>
<button
type="submit"
disabled={!input.trim() || state === 'simulating'}
className="flex items-center justify-center rounded-sm bg-[#00a884] px-4 py-2 text-white transition-colors hover:bg-[#008f6f] disabled:opacity-40 shadow-sm"
>
<Send size={14} />
</button>
</form>
<div className="mt-2 flex items-center justify-center gap-4 opacity-20 pointer-events-none">
<span className="text-[8px] font-mono tracking-widest uppercase">E2E Encrypted</span>
<span className="text-[8px] font-mono tracking-widest uppercase">NotiFlow v2.0</span>
</div>
</div>
</div>
)
}