'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { Send, Bot, Phone, ArrowLeft } from 'lucide-react'; import { cn, formatPhone } from '@/lib/utils'; import { MessageBubble, hashColor, getInitials } from './message-bubble'; interface Message { id: string; contact_id: string; direction: 'inbound' | 'outbound'; source: string; body: string; created_at: string; } interface Contact { id: string; phone: string; name: string | null; status: string; email: string | null; message_count: number; first_contact: string; last_contact: string; } interface ChatThreadProps { contactId: string; onBack?: () => void; } export function ChatThread({ contactId, onBack }: ChatThreadProps) { const [contact, setContact] = useState(null); const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [sending, setSending] = useState(false); const [loading, setLoading] = useState(true); const scrollRef = useRef(null); const inputRef = useRef(null); const scrollToBottom = useCallback(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, []); // Fetch conversation data const fetchData = useCallback(async () => { try { const res = await fetch(`/api/conversations/${contactId}`); if (res.ok) { const data = await res.json(); setContact(data.contact); setMessages(data.messages || []); } } catch (err) { console.error('Failed to load conversation:', err); } finally { setLoading(false); } }, [contactId]); useEffect(() => { setLoading(true); fetchData(); const interval = setInterval(fetchData, 5000); return () => clearInterval(interval); }, [fetchData]); // Auto-scroll on new messages useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); // Focus input when conversation loads useEffect(() => { if (!loading) inputRef.current?.focus(); }, [loading, contactId]); const handleSend = async () => { const text = input.trim(); if (!text || sending) return; setSending(true); setInput(''); // Optimistic update const optimistic: Message = { id: `tmp-${Date.now()}`, contact_id: contactId, direction: 'outbound', source: 'manual', body: text, created_at: new Date().toISOString(), }; setMessages((prev) => [...prev, optimistic]); try { const res = await fetch(`/api/conversations/${contactId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: text }), }); if (!res.ok) { // Remove optimistic message on failure setMessages((prev) => prev.filter((m) => m.id !== optimistic.id)); console.error('Failed to send message'); } else { // Replace optimistic with real message const data = await res.json(); setMessages((prev) => prev.map((m) => (m.id === optimistic.id ? { ...optimistic, id: data.messageId || optimistic.id } : m)) ); } } catch { setMessages((prev) => prev.filter((m) => m.id !== optimistic.id)); } finally { setSending(false); inputRef.current?.focus(); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }; if (loading) { return (
); } if (!contact) { return (

Contact not found

); } const displayName = contact.name || formatPhone(contact.phone); const avatarColor = hashColor(displayName); const initials = getInitials(contact.name || contact.phone.slice(-4)); return (
{/* Header */}
{/* Mobile back button */} {onBack && ( )} {/* Avatar */}
{initials}

{contact.name || 'Unknown'}

CloseBot SMS AI
{formatPhone(contact.phone)}
{/* Status */}
Active
{/* Messages area */}
{messages.length === 0 ? (

No messages yet

Send a message to start the conversation

) : ( <> {/* Date separator for first message */}
{new Date(messages[0].created_at).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric', })}
{messages.map((msg, i) => { // Show avatar only for first message in a sequence from same direction const prevMsg = i > 0 ? messages[i - 1] : null; const showAvatar = !prevMsg || prevMsg.direction !== msg.direction; return ( ); })} )}
{/* Message input */}
setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type a message..." className="flex-1 bg-transparent text-sm text-slate-100 placeholder:text-slate-500 focus:outline-none" disabled={sending} />
); }