'use client'; import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { api } from '@/lib/api/client'; import { Search, Send, Mail, MessageSquare, Star, Plus, ArrowLeft, Phone, MoreVertical, Paperclip, Smile, Check, CheckCheck, Clock, X, Inbox, Users } from 'lucide-react'; // Types interface Contact { id: string; name: string; email: string; phone: string; avatar?: string; } interface Message { id: string; conversationId: string; content: string; timestamp: Date; direction: 'inbound' | 'outbound'; status: 'sent' | 'delivered' | 'read' | 'pending'; channel: 'sms' | 'email'; } interface Conversation { id: string; contact: Contact; lastMessage: string; lastMessageTime: Date; unreadCount: number; isStarred: boolean; channel: 'sms' | 'email'; messages: Message[]; } type FilterTab = 'all' | 'unread' | 'starred'; // Mock data const mockConversations: Conversation[] = [ { id: '1', contact: { id: 'c1', name: 'John Smith', email: 'john.smith@example.com', phone: '+1 (555) 123-4567' }, lastMessage: 'I am very interested in the property on Main Street. When can we schedule a viewing?', lastMessageTime: new Date(Date.now() - 1000 * 60 * 5), // 5 minutes ago unreadCount: 2, isStarred: true, channel: 'sms', messages: [ { id: 'm1', conversationId: '1', content: 'Hi, I saw your listing for the commercial property on Main Street.', timestamp: new Date(Date.now() - 1000 * 60 * 60), direction: 'inbound', status: 'read', channel: 'sms' }, { id: 'm2', conversationId: '1', content: 'Hello John! Yes, it is still available. Would you like to schedule a viewing?', timestamp: new Date(Date.now() - 1000 * 60 * 45), direction: 'outbound', status: 'read', channel: 'sms' }, { id: 'm3', conversationId: '1', content: 'That would be great. What times work for you?', timestamp: new Date(Date.now() - 1000 * 60 * 30), direction: 'inbound', status: 'read', channel: 'sms' }, { id: 'm4', conversationId: '1', content: 'I have availability tomorrow at 2 PM or Thursday at 10 AM.', timestamp: new Date(Date.now() - 1000 * 60 * 20), direction: 'outbound', status: 'delivered', channel: 'sms' }, { id: 'm5', conversationId: '1', content: 'I am very interested in the property on Main Street. When can we schedule a viewing?', timestamp: new Date(Date.now() - 1000 * 60 * 5), direction: 'inbound', status: 'delivered', channel: 'sms' }, ] }, { id: '2', contact: { id: 'c2', name: 'Sarah Johnson', email: 'sarah.j@realty.com', phone: '+1 (555) 234-5678' }, lastMessage: 'Thank you for the documents. I will review and get back to you.', lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago unreadCount: 0, isStarred: false, channel: 'email', messages: [ { id: 'm6', conversationId: '2', content: 'Hi Sarah, please find attached the property documents.', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 3), direction: 'outbound', status: 'read', channel: 'email' }, { id: 'm7', conversationId: '2', content: 'Thank you for the documents. I will review and get back to you.', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 2), direction: 'inbound', status: 'read', channel: 'email' }, ] }, { id: '3', contact: { id: 'c3', name: 'Michael Chen', email: 'mchen@business.com', phone: '+1 (555) 345-6789' }, lastMessage: 'Perfect, I will bring the signed lease agreement.', lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago unreadCount: 1, isStarred: true, channel: 'sms', messages: [ { id: 'm8', conversationId: '3', content: 'Michael, just confirming our meeting tomorrow at 3 PM.', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 25), direction: 'outbound', status: 'read', channel: 'sms' }, { id: 'm9', conversationId: '3', content: 'Perfect, I will bring the signed lease agreement.', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), direction: 'inbound', status: 'delivered', channel: 'sms' }, ] }, { id: '4', contact: { id: 'c4', name: 'Emily Davis', email: 'emily.davis@corp.com', phone: '+1 (555) 456-7890' }, lastMessage: 'Looking forward to discussing the investment opportunity.', lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 48), // 2 days ago unreadCount: 0, isStarred: false, channel: 'email', messages: [ { id: 'm10', conversationId: '4', content: 'Looking forward to discussing the investment opportunity.', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 48), direction: 'inbound', status: 'read', channel: 'email' }, ] }, { id: '5', contact: { id: 'c5', name: 'Robert Wilson', email: 'rwilson@investors.com', phone: '+1 (555) 567-8901' }, lastMessage: 'Can you send me the updated financials?', lastMessageTime: new Date(Date.now() - 1000 * 60 * 60 * 72), // 3 days ago unreadCount: 0, isStarred: false, channel: 'sms', messages: [ { id: 'm11', conversationId: '5', content: 'Can you send me the updated financials?', timestamp: new Date(Date.now() - 1000 * 60 * 60 * 72), direction: 'inbound', status: 'read', channel: 'sms' }, ] }, ]; // Avatar color palette based on name const avatarColors = [ 'bg-blue-500', 'bg-emerald-500', 'bg-violet-500', 'bg-amber-500', 'bg-rose-500', 'bg-cyan-500', 'bg-indigo-500', 'bg-pink-500', ]; function getAvatarColor(name: string): string { const charSum = name.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0); return avatarColors[charSum % avatarColors.length]; } function getInitials(name: string): string { const parts = name.trim().split(' ').filter(Boolean); if (parts.length >= 2) { return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); } return name.substring(0, 2).toUpperCase(); } // Helper functions function formatMessageTime(date: Date): string { const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / (1000 * 60)); const hours = Math.floor(diff / (1000 * 60 * 60)); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); if (minutes < 1) return 'Now'; if (minutes < 60) return `${minutes}m`; if (hours < 24) return `${hours}h`; if (days === 1) return 'Yesterday'; if (days < 7) return date.toLocaleDateString([], { weekday: 'short' }); return date.toLocaleDateString([], { month: 'short', day: 'numeric' }); } function formatFullTime(date: Date): string { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } // Components // Avatar Component function Avatar({ name, size = 'md', showOnlineIndicator = false }: { name: string; size?: 'sm' | 'md' | 'lg'; showOnlineIndicator?: boolean; }) { const sizeClasses = { sm: 'w-8 h-8 text-xs', md: 'w-11 h-11 text-sm', lg: 'w-14 h-14 text-lg' }; return (
{getInitials(name)}
{showOnlineIndicator && (
)}
); } // Conversation List Item const ConversationItem = React.memo(function ConversationItem({ conversation, isSelected, onClick }: { conversation: Conversation; isSelected: boolean; onClick: () => void; }) { const isUnread = conversation.unreadCount > 0; return (
{/* Avatar with channel indicator */}
{conversation.channel === 'sms' ? ( ) : ( )}
{/* Content */}
{conversation.contact.name} {conversation.isStarred && ( )}
{formatMessageTime(conversation.lastMessageTime)}

{conversation.lastMessage}

{isUnread && ( {conversation.unreadCount} )}
); }); // Conversation List (Left Panel) function ConversationList({ conversations, selectedId, onSelect, searchQuery, onSearchChange, activeFilter, onFilterChange, isLoading, onNewMessage }: { conversations: Conversation[]; selectedId: string | null; onSelect: (id: string) => void; searchQuery: string; onSearchChange: (query: string) => void; activeFilter: FilterTab; onFilterChange: (filter: FilterTab) => void; isLoading: boolean; onNewMessage: () => void; }) { const filters: { id: FilterTab; label: string; count?: number }[] = [ { id: 'all', label: 'All' }, { id: 'unread', label: 'Unread', count: conversations.filter(c => c.unreadCount > 0).length }, { id: 'starred', label: 'Starred', count: conversations.filter(c => c.isStarred).length }, ]; return (
{/* Header */}

Messages

{/* Search */}
onSearchChange(e.target.value)} className={`w-full pl-12 ${searchQuery ? 'pr-12' : 'pr-4'} py-3 bg-muted/50 border border-border/50 rounded-xl text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/50 transition-all`} /> {searchQuery && ( )}
{/* Filter Tabs */}
{filters.map((filter) => ( ))}
{/* Conversation List */}
{isLoading ? (

Loading conversations...

) : conversations.length === 0 ? (
{searchQuery ? ( ) : activeFilter === 'unread' ? ( ) : activeFilter === 'starred' ? ( ) : ( )}

{searchQuery ? 'No results found' : activeFilter === 'unread' ? 'All caught up!' : activeFilter === 'starred' ? 'No starred conversations' : 'No conversations yet' }

{searchQuery ? 'Try a different search term' : activeFilter === 'unread' ? 'You have no unread messages' : activeFilter === 'starred' ? 'Star important conversations to find them here' : 'Start a conversation to get going' }

{!searchQuery && activeFilter === 'all' && ( )}
) : ( conversations.map((conversation) => ( onSelect(conversation.id)} /> )) )}
); } // Message Bubble function MessageBubble({ message }: { message: Message }) { const isOutbound = message.direction === 'outbound'; const StatusIcon = () => { switch (message.status) { case 'pending': return ; case 'sent': return ; case 'delivered': return ; case 'read': return ; default: return null; } }; return (

{message.content}

{formatFullTime(message.timestamp)} {isOutbound && }
); } // Message Composer function MessageComposer({ channel, onChannelChange, message, onMessageChange, onSend, isSending }: { channel: 'sms' | 'email'; onChannelChange: (channel: 'sms' | 'email') => void; message: string; onMessageChange: (message: string) => void; onSend: () => void; isSending: boolean; }) { const textareaRef = useRef(null); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (message.trim()) { onSend(); } } }; useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = 'auto'; textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 150) + 'px'; } }, [message]); return (
{/* Channel Toggle */}
Send via:
{/* Composer */}